Skip to content

Commit dacdba2

Browse files
committed
Trait and Generics
1 parent fbdf045 commit dacdba2

File tree

4 files changed

+300
-6
lines changed

4 files changed

+300
-6
lines changed

chapter11/example1/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9+
10+
serde = "1.0.0"
11+
serde_json = "1.0.0"

chapter11/example1/README.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# トレイトとジェネリクス
2+
3+
## トレイトの使い方
4+
5+
トレイトは、任意の型がサポートすることができる性質。
6+
多くの場合トレイトは何らかの機能、つまりその型ができることを表す。
7+
8+
* std::io::Writeを実装した値には、バイト列を書き込むことができる。
9+
* std::iter::Iteratorを実装した値は、値の列を生成することができる。
10+
* std::clone::Cloneを実装した値は、自分のクローンをメモリ上に作成することができる。
11+
* std::fmt::Debugを実装した値は、デバッグ表示を生成することができる。
12+
13+
これらのトレイトは全て、Rustの標準ライブラリに含まれている。
14+
15+
* std::fs::Fileはstd::io::Writeを実装している。
16+
* Range<i32> (0..10の型)hはIteratorを実装している。スライスやハッシュテーブルに関するイテレータも同様。
17+
* 標準ライブラリのほとんどの型はCloneを実装している。TcpStreamのように、メモリ内のデータいが愛のものを表している型は例外だ。
18+
* 標準ライブラリのほとんどの型はDebugを実装している。
19+
20+
### トレイトオブジェクト
21+
22+
トレイトを使って多様性のあるコードを書く方法は2つある。トレイトオブジェクトとジェネリクスだ。
23+
24+
RustではWrite型の変数を持つことができない。
25+
Write型の変数を持つことができるのは、Writeトレイトを実装した具体的な型だけだ。
26+
27+
```rust
28+
use std::io::Write;
29+
let mut buf: Vec<u8> = vec![];
30+
let mut writer: Write = buf; // エラー;Writeはサイズが不定
31+
```
32+
33+
変数のサイズはコンパイル時に、決まってなければならない。しかし、Writeはどんな方でも実装できるので、サイズが不定である。
34+
35+
Rustでは、参照だということを明示しなければならない。
36+
```rust
37+
use std::io::Write;
38+
let mut buf: Vec<u8> = vec![];
39+
let mut writer: &mut Write = &mut buf; // ok
40+
```
41+
42+
上のwriterのようなトレイト型への参照を、トレイトオブジェクトっと呼ぶ。他の参照と同様に、トレイトオブジェクトは、何らかの値
43+
を指し、生存期間を持ち、mutか共有可能かのどちらかだ。
44+
45+
トレイトオブジェクトが特殊なのは、コンパイル時に参照先の実際の値がわからないことだ。そこで、トレイトオブジェクトには参照先の型に
46+
関する情報が追加されている。この情報は、Rustが舞台裏でだけ使う。writer.write(data)が呼び出されると、*writerの型に応じた
47+
正しいwriteメソッドを動的に呼び出すために、型の情報が必要になる。直接型情報を問いあ合わせることはないし、&mut Writeから、
48+
例えばVec<u8>のような実際の型にダウンキャストすることもできない。
49+
50+
```rust
51+
use std::io::Write;
52+
53+
fn write_all<W: Write>(writer: &mut W, buf: &[u8]) {
54+
writer.write_all(buf).unwrap();
55+
}
56+
```
57+
58+
### トレイトオブジェクトのメモリ配置
59+
60+
メモリ上ではトレイトオブジェクトはファットポインタで、値へのポインタと型情報へのポインタを持っている。
61+
個々のトレイトオブジェクトは、2ワード長ということになる。
62+
63+
Rustは、通常の参照を必要に応じて自動的にトレイトオブジェクトに変換する。この例では &mut local_fileをsay_helloに渡すことができるのは
64+
このおかげである。
65+
```rust
66+
let mut local_file = File::create("hello.txt)?;
67+
say_hello(&mut loca_file)?;
68+
```
69+
70+
&mut local_fileの型は&mut Fileで、say_helloの引数の型は&mut Writeだ。FileはWriteの一部なので、通常の参照からトレイトオブジェクト
71+
への変換は自動的に行われる。
72+
同様にBox<File>かあらBox<Write>への変換も可能だ。ヒープ上にあるWriteを実装した型の値を所有する値となる。
73+
74+
```rust
75+
let w: Box<Write> = Box::new(local_file);
76+
```
77+
78+
```rust
79+
let rc: Rc<Write> = Rc::new(local_file);
80+
```
81+
82+
### ジェネリック関数
83+
84+
say_hello関数をジェネリック関数で書き直すと以下の通り。
85+
```rust
86+
/// generic function
87+
fn say_hello<W: Write>say_hello(out: &mut W) -> io::Result<()> {
88+
out.write_all(b"Hello, world!\n");
89+
out.flush();
90+
}
91+
```
92+
変わったのはシグネチャ部分だけで、関数の本体は変わっていない。ジェネリック関数は、トレイトオブジェクトを使う場合には、
93+
トレイトオブジェクトを引数として受け取ることができない。トレイトオブジェクトは、参照でなければならないためだ。
94+
```rust
95+
/// plain function
96+
fn say_hello(out: &mut Write) -> io::Result<()> {
97+
out.write_all(b"Hello, world!\n");
98+
out.flush();
99+
}
100+
```
101+
102+
<W: Write> の部分により、関数がジェネリックになる。この部分が**型パラメータ**で、関数のボディー部全体を通してWはWriteトレイトを実装した
103+
何らかの型だ、ということを示している。この関数は、どんな型の引数も受け取ることができるが、その型はWriteトレイトを実装していなければならない。
104+
&mut local_fileを渡すと、say_helllo<File>()を呼び出したことになる。
105+
&mut bytesを渡すと、say_hello<Vec<u8>>()を呼び出したことになる。
106+
107+
いずれの場合も、コンパイラはWを引数の型から推論する。次のように、型パラメータを明示的に指定することもできる。
108+
```rust
109+
say_hello::<File>(&mut local_file)?;
110+
```
111+
112+
ヒントになるような引数を持たないジェネリック関数の場合には明示的に書く必要がある。
113+
114+
```rust
115+
/// calling a generic method colllect<C>() that takes no arguments
116+
let v1 =(0..1000).collect; // エラー
117+
let v2 = (0..1000).collect::<Vec<i32>>(); // ok
118+
```
119+
120+
型パラメータに複数の機能を要求したい場合がある。例えば、ベクタの中から頻度の高い値のTop10を表示したいとしよう。
121+
その場合は表示可能である必要がある。
122+
123+
code top_ten
124+
125+
次の例が表すように、制約は簡単には理解できないほど長くなることがある。そこで、Rustにはキーワードwhereがある。
126+
whereキーワードを使うと、型パラメータの制約を関数のシグネチャから分離することができる。
127+
128+
```rust
129+
fn run_query<M, R>(query: &str) -> Result<R, M::Error>
130+
where
131+
M: Database,
132+
R: FromRow,
133+
{
134+
let mut conn = M::connect("localhost")?;
135+
let result = conn.run_query(query)?;
136+
Ok(R::from_row(result))
137+
}
138+
```
139+
140+
whereキーワードを使うと、型パラメータの制約を関数のシグネチャから分離することができる。この例では、MはDatabaseを実装し、RはFromRowを実装している必要がある。
141+
142+
生存期間パラメータの構文は、ジェネリック関数は、生存期間パラメータと型パラメータの両方を持つことができる。
143+
144+
```rust
145+
/// Return a reference to the point in `candidates` that`s
146+
/// closet to the `target` point.
147+
fn nearest<'t, 'c, P>(target: &'t P, candidates: &'c [P]) -> &'c P
148+
where
149+
P: MeasureDiistance
150+
{
151+
...
152+
}
153+
```
154+
155+
### どちらを使うべきか
156+
157+
トレイトオブジェクトとジェネリック関数のどちらを使うかは、微妙な問題だ。どちらもトレイトに基づいているので、多くの点が共通している。
158+
複数の方が入り混じっているコレクションを扱う場合には、トレイトオブジェクトを使うのが正しい。
159+
ジェネリックなサラダを作ることも技術的には可能だ。
160+
161+
162+
```rust
163+
trait Vegetable {
164+
165+
}
166+
struct Salad<V: Vegettable> {
167+
veggies: Vec<V>,
168+
}
169+
```
170+
しかしこれは厳しい、個々のサラダは常に同じ種類の野菜を持つことになる。ジェネリックなサラダは、異なる種類の野菜を混ぜることができない。
171+
トレイトオブジェクトを使うと、異なる種類の野菜を混ぜることができる。
172+
173+
```rust
174+
struct Salad {
175+
veggies: Vec<Box<Vegetable>>,
176+
}
177+
```
178+
179+
## トレイトの定義と実装
180+
181+
トレイトの定義は簡単だ。トレイトの名前と、トレイトが提供するメソッドのシグネチャを書くだけである。
182+
183+
```rust
184+
trait Animal {
185+
fn noise(&self) -> &'static str;
186+
}
187+
```
188+
189+
トレイトを実装するには、implブロックを使う。implブロックは、トレイトの名前と、トレイトを実装する型の名前を指定する。
190+
191+
```rust
192+
struct Dog;
193+
impl Animal for Dog {
194+
fn noise(&self) -> &'static str {
195+
"bark"
196+
}
197+
}
198+
```
199+
200+
### トレイトと他人の定義した型
201+
202+
任意の型に対して任意のトレイトを実装することができる。ただし、トレイトか型のどちらかがそのスコープで新たに導入されている場合に限る。
203+
204+
```rust
205+
trait IsEmoji {
206+
fn is_emoji(&self) -> bool;
207+
}
208+
209+
impl IsEmoji for char {
210+
fn is_emoji(&self) -> bool {
211+
...
212+
}
213+
}
214+
```
215+
216+
#### 拡張トレイト
217+
218+
ジェネリックImplブロックを書いて、様々な型に対して一気に拡張トレイトを追加することもできる。
219+
以下に示す拡張トレイトは、Rustの全てのWriteを実装した型に対してメソッドを追加する。
220+
221+
```rust
222+
use std::io::{self, Write}
223+
224+
/// Trait for values to which you can send HTML.
225+
trait WriteHtml {
226+
fn write_html(&mut self, html: &HtmlDocument) -> io::Result<()>;
227+
}
228+
impl<W: Write> WrittHtml for W {
229+
fn write_html(&mut self, html: &HtmlaDocument) -> io::Result<()> {
230+
...
231+
}
232+
}

chapter11/example1/configuration.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"Alice","age":"20"}

chapter11/example1/src/main.rs

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::fmt::Debug;
23
use std::hash::Hash;
34
use std::io::Write;
@@ -29,7 +30,7 @@ fn main() {
2930
println!("writer: {:?}", v1)
3031
}
3132

32-
// reference of trait
33+
/// reference of trait
3334
fn trait_object() -> std::io::Result<()> {
3435
let mut buf: Vec<u8> = vec![];
3536
let writer: &mut dyn Write = &mut buf;
@@ -51,15 +52,72 @@ fn applender<W: Write>(out: &mut W, s: &str) -> std::io::Result<()> {
5152
out.flush()
5253
}
5354

54-
/// .
55-
// where type
56-
fn _top_ten<T: Debug + Hash + Eq>(_values: &Vec<T>) {
57-
todo!()
55+
/// Prints the top ten items in a list.
56+
pub fn top_ten<T>(list: &Vec<T>)
57+
where
58+
T: Eq + Hash + Debug,
59+
{
60+
let mut map = HashMap::new();
61+
for item in list {
62+
let count = map.entry(item).or_insert(0);
63+
*count += 1;
64+
}
65+
let mut pairs: Vec<_> = map.into_iter().collect();
66+
pairs.sort_by(|&(_, a), &(_, b)| b.cmp(&a));
67+
for (item, count) in pairs.into_iter().take(10) {
68+
println!("{:?}: {}", item, count);
69+
}
5870
}
59-
6071
fn _top_twenty<T: Debug + Hash + Eq>(_values: &Vec<T>)
6172
where
6273
T: Debug + Hash + Eq,
6374
{
6475
todo!()
6576
}
77+
78+
/// default method
79+
/// A writer that ignores whatever data yoou write to it.
80+
pub struct Sink;
81+
82+
impl Write for Sink {
83+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
84+
Ok(buf.len())
85+
}
86+
fn flush(&mut self) -> std::io::Result<()> {
87+
Ok(())
88+
}
89+
90+
// write_allはデフォルト実装がか書かれているのでそれを利用する。
91+
}
92+
93+
/// seadeライブラリ
94+
use serde::{Deserialize, Serialize};
95+
use serde_json::json;
96+
pub fn save_configuration(config: &HashMap<String, String>) -> std::io::Result<()> {
97+
// Create a JSON serializer to write the data to a file.
98+
let writer = File::create("configuration.txt")?;
99+
let mut serializer = serde_json::Serializer::new(writer);
100+
config.serialize(&mut serializer)?;
101+
Ok(())
102+
}
103+
104+
#[cfg(test)]
105+
mod tests {
106+
use super::*;
107+
108+
#[test]
109+
fn test_top_ten() {
110+
let list = vec![
111+
1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
112+
];
113+
top_ten(&list);
114+
}
115+
116+
#[test]
117+
fn test_save_configuration() {
118+
let mut config = HashMap::new();
119+
config.insert("name".to_string(), "Alice".to_string());
120+
config.insert("age".to_string(), "20".to_string());
121+
save_configuration(&config).unwrap();
122+
}
123+
}

0 commit comments

Comments
 (0)