目次

Rustの勉強[ジェネリクス型、トレイト、ライフタイム編]

(更新: )ぎじゅつ

はじめに

#

NO IMAGEThe Rust Programming Language 日本語版 - The Rust Programming Language 日本語版
を読んでいる

目標100個まだ考えられてないけども、この後極寒のコインランドリーで考えることにした。

前回の振り返り

#

今日読む場所

#

お勉強

#

メモ

#
  • ジェネリクスきたーーーーーーーーーーーーー
  • 型の賢さがわかった後に柔軟性がすごく好きになった

    それから、トレイトを使用して、ジェネリックな方法で振る舞いを定義する方法を学びます。

  • おい、トレイトわからんぞ

コードを複製することは退屈ですし、間違いも起きやすいです。また、 コードを変更したい時に複数箇所、更新しなければなりません。

  • そらそうじゃ
fn largest(list: &[i32]) -> i32 {
    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);
    assert_eq!(result, 100);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let result = largest(&number_list);
    println!("The largest number is {}", result);
    assert_eq!(result, 6000);
}
  • 俺だったら絶対こうやって書くな
  • あーなるほど、この説明したのちにジェネリクスの作り方の説明がしたかったわけか

"type"の省略形なので、Tが多くのRustプログラマの既定の選択なのです。

  • はぇーJavaでも同じなんだろうか

Tがなりうる全ての可能性のある型に対して動作しないと述べています。

  • それはそう
  • というかそこまでチェックしてくれるのか、すごいね
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let wont_work = Point { x: 5, y: 4.0 };
}

この例で、xに整数値5を代入すると、このPointのインスタンスに対するジェネリックな型Tは整数になるとコンパイラに知らせます。 それからyに4.0を指定する時に、このフィールドはxと同じ型と定義したはずなので、このように型不一致エラーが出ます:

  • おお、あたりまえといえばそうだが、Tの型はTだからジェネリクスであるが同じ型なのか
struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

所望の数だけ定義でジェネリックな型引数を使用できますが、 数個以上使用すると、コードが読みづらくなります。コードで多くのジェネリックな型が必要な時は、 コードの小分けが必要なサインかもしれません。

  • そうだよなー、ジェネリクスが二つ必要な場面が思いうかばない
#![allow(unused)]
fn main() {
    enum Option<T> {
        Some(T),
        None,
    }
}
#![allow(unused)]
fn main() {
    enum Result<T, E> {
        Ok(T),
        Err(E),
    }
}
  • enumすげーーーーーーーーーーーーーーー
  • あ、たしかにResultはT, Eとして2種類ジェネリクス欲しいかもね
    • EはErrのEなんだろうか
#![allow(unused)]
fn main() {
    struct Point<T> {
        x: T,
        y: T,
    }

    impl Point<f32> {
        fn distance_from_origin(&self) -> f32 {
            (self.x.powi(2) + self.y.powi(2)).sqrt()
        }
    }
}
  • あ、へぇ~~~~~特定の型のみの実装ができるんだ

ジェネリックな型引数を使用すると、実行時にコストが発生するのかな、と思うかもしれません。 嬉しいことにRustでは、ジェネリクスを、具体的な型があるコードよりもジェネリックな型を使用したコードを実行するのが遅くならないように実装しています。
コンパイラはこれを、ジェネリクスを使用しているコードの単相化をコンパイル時に行うことで達成しています。 単相化(monomorphization)は、コンパイル時に使用されている具体的な型を入れることで、 ジェネリックなコードを特定のコードに変換する過程のことです。

  • は?天才じゃん
#![allow(unused)]
fn main() {
    let integer = Some(5);
    let float = Some(5.0);
}

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

になると

  • すげーじゃんか

  • 勉強時間を引延してトレイトにだけ足を突っ込んでおく

    • フワッとしかわからんかった
#![allow(unused)]
fn main() {
    pub trait Summary {
        fn summarize(&self) -> String;
    }

    pub struct NewsArticle {
        pub headline: String,
        pub location: String,
        pub author: String,
        pub content: String,
    }

    impl Summary for NewsArticle {
        fn summarize(&self) -> String {
            format!("{}, by {} ({})", self.headline, self.author, self.location)
        }
    }

    pub struct Tweet {
        pub username: String,
        pub content: String,
        pub reply: bool,
        pub retweet: bool,
    }

    impl Summary for Tweet {
        fn summarize(&self) -> String {
            format!("{}: {}", self.username, self.content)
        }
    }
}
  • これを見ながら考えるか
  • impl Summary for NewsArticle ここか
  • これちなみにSummaryという型を引数に関数書けるんだろうか

    これにより、自分の型にSummaryを実装することが可能になるでしょう。Summaryトレイトは、 他のクレートが実装するためには、公開トレイトである必要があり、ここでは、リスト10-12のtraitの前に、 pubキーワードを置いたのでそうなっています。

  • なるほどー、ちょっと情報過多になってきたかもな

しかし、外部のトレイトを外部の型に対して実装することはできません。例として、 aggregatorクレート内でVecに対してDisplayトレイトを実装することはできません。 DisplayとVecは標準ライブラリで定義され、aggregatorクレートに固有ではないからです。 この制限は、コヒーレンス(coherence)、特に孤児のルール(orphan rule)と呼ばれるプログラムの特性の一部で、 親の型が存在しないためにそう命名されました。この規則により、他の人のコードが自分のコードを壊したり、 その逆が起きないことを保証してくれます。この規則がなければ、2つのクレートが同じ型に対して同じトレイトを実装できてしまい、 コンパイラはどちらの実装を使うべきかわからなくなってしまうでしょう。

  • この辺り大事そうだから明日読もう

まとめ

#
  • ジェネリクスやった、おもしろい
    • TはジェネリクスだがTとして型は一緒ってのは目から鱗だった
  • トレイトはinterfaceっぽかったな