Rustの勉強を公式ドキュメントを見ながらしているっピけど…ジェネリック境界とかトレイトって単語で詰んだっピ…
わからなすぎてタコピーになってしまったっピ…。
自分で理解するために解説記事を書くっピけど、間違えてたら教えてほしいっピ!
とりあえずまだ読んでいない人は、まずはタコピーの原罪を読むっピ。話はそれからだっピ。
ジェネリックハッピー道具
まずはジェネリックデータ型を理解するっピ!薬局に売ってそうなデータ型だっピね。
トレイト境界を理解するには、これををきちんと理解してないとだめだっピね!しずかちゃんは僕が守るっピ!
関数を作りたい時に、引数とか返り値がなんのデータ型になるかがわからなくて、こんな感じで同じことをする関数を2つ作っちゃうことがあるだっピね。
// リストの中で一番大きな整数を返す
fn largest_i32(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
// リストの中で一番後ろの方のアルファベットを返す
fn largest_char(list: &[char]) -> char {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result); // The largest number is 100
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result); // The largest char is y
}
largest_i32()
もlargest_char()
も引数の型が違うだけで、やってることは同じだっピな。
こんな時に使えるハッピー道具があるっピ!それがジェネリックなデータ型だっピ!
Rustではジェネリックなデータ型はこうやって定義するッピ。
fn largest<T>(list: &[T]) -> T {}
<T>
はこの関数がジェネリックデータ型を使うって宣言だっピ。ちなみにT
はtypeのT
だっピな。
関数largest()
はT
型の値のスライスをlist
として引数に持ピ、同じT
型の値を返すっピね。
そして、この通りに関数largest()
を書き直すとこうなるっピけど、これはコンパイルできないっピ…。
fn largest<T>(list: &[T]) -> T { //エラー
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
エラーはこうなるっってここに書いてあったっピ。
error[E0369]: binary operation `>` cannot be applied to type `T`
(エラー: 2項演算`>`は、型`T`に適用できません)
--> src/main.rs:5:12
|
5 | if item > largest {
| ^^^^^^^^^^^^^^
|
= note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
(注釈: `std::cmp::PartialOrd`の実装が`T`に対して存在しない可能性があります)
ぼくの星ではこうなったっピ。
error[E0369]: binary operation `>` cannot be applied to type `T`
(エラー: 2項演算`>`は、型`T`に適用できません)
--> main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++
error: aborting due to previous error
どちらにせよ、ここでやっとトレイトが出てくるっピ!std::cmp::PartialOrd
がトレイトだっピな!
このエラーはトレイトを理解することで解決できるっピ
トレイトの救済
もう一度エラーを見るっピ。
error[E0369]: binary operation `>` cannot be applied to type `T`
(エラー: 2項演算`>`は、型`T`に適用できません)
--> main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++
error: aborting due to previous error
エラーのは、「T
が、なりうる全ての可能性のある型に対して動作しない」っていってるっピ。よくわかんないッピ…。
要するに、>
はT
の任意の型で使えるわけじゃないってことだっピ!T
にはいろんな型がはいるっピ。例えば複素数とかは>
が使えない一つの例だっピね。
もう勘のいいしずかちゃんは気づいてるだっピね。
>
が使える集合だけにlargest()
を実装するために、トレイトがいるっピ。
ここではstd::cmp::PartialOrd
ってトレイトを指定することで、このエラーは解決できるっピ。
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
ちなみにPartialOrd
は日本語でいうと半順序集合のことで、RustのPartialOrd
についてはここに書いてあるっピ。
知らない人は、自然数とか整数とかの順番を付けれるものをそう呼ぶって思ってほしいっピ。
これで全部解決!しずかちゃんもきっと笑顔になるっピ!
実行っピ!
error[E0508]: cannot move out of type `[T]`, a non-copy slice
--> main.rs:2:23
|
2 | let mut largest = list[0];
| ^^^^^^^
| |
| cannot move out of here
| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
| help: consider borrowing here: `&list[0]`
error[E0507]: cannot move out of a shared reference
--> main.rs:4:18
|
4 | for &item in list.iter() {
| ----- ^^^^^^^^^^^
| ||
| |data moved here
| |move occurs because `item` has type `T`, which does not implement the `Copy` trait
| help: consider removing the `&`: `item`
error: aborting due to 2 previous errors
え?
トレイトの告解
まだ終わってないっピ!エラーをよく見るっピ。
error[E0508]: cannot move out of type `[T]`, a non-copy slice
//コピースライスではない[T]からmoveをすることはできません
︰
largest()
をジェネリックにしたことで、Copy
ではない型を含む可能性が出てきたんだっピね。
Copy
っていうのはサイズが既知でスタックに格納される型だっピ。逆にString
とかはヒープに保存されるCopy
じゃない代表だっピね。詳しくはここだっピ。
ここでは、Copy
トレイトしか使わないことにしてstd::cmp::PartialOrd
にCopy
を追加するっピ!
fn largest<T: std::cmp::PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
これで期待通りの動きになるっピ!
まとめ(結論)
トレイトっていうのはジェネリックな型を一部に限定する全体集合に対する部分集合みたいなものだと認識したっピ
今回のイメージはこんな感じだっピな。字が汚くてごめんっピ…。
これでトレイトともジェネリックともハッピーお友達になれたっピね!