LoginSignup
2
1

More than 1 year has passed since last update.

【Rust】ジェネリック境界わかんないッピ…

Last updated at Posted at 2022-04-20

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::PartialOrdCopyを追加するっピ!

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
}

これで期待通りの動きになるっピ!

まとめ(結論)

トレイトっていうのはジェネリックな型を一部に限定する全体集合に対する部分集合みたいなものだと認識したっピ
今回のイメージはこんな感じだっピな。字が汚くてごめんっピ…。

toreito.png

これでトレイトともジェネリックともハッピーお友達になれたっピね!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1