目次

Rustの勉強[トレイト その2]

(更新: )ぎじゅつ

はじめに

#

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

今年は年始から色々悩んでいる気がするがこの取り組みだけは継続したいのでやるぞーーーーー!

前回の振り返り

#
  • ジェネリクスとトレイトのちょっと
    • 概ね認識にズレはなかった
    • 型安全って凄いね

今日読む場所

#

お勉強

#

メモ

#
  • トレイトやるぞー
    • 余談だけどやっぱり、トレイトを事前に予測して実装をするのは無理だよな
    • いらない制約が生れまくる予感しかしない
  • トレイトの概要、すっごいJavaな感性だな
    • まあJavaエンジニアでそれを中心に見てきたからってのもあるかもだけど
    • 使い方がまともな場所は見たことがない

デフォルト実装は、自らのトレイトのデフォルト実装を持たない他のメソッドを呼び出すことができます。 このようにすれば、トレイトは多くの有用な機能を提供しつつ、実装者は僅かな部分しか指定しなくて済むようになります。 例えば、Summaryトレイトを、(実装者が)内容を実装しなければならないsummarize_authorメソッドを持つように定義し、 それからsummarize_authorメソッドを呼び出すデフォルト実装を持つsummarizeメソッドを定義することもできます:

  • なんか一言ではまったくわからなかったから噛み砕く
    • んあー、わかったわ
    • 仮実装としてトレイトのメソッドを実装しておいて、それを別のメソッドで呼ぶってことね
#![allow(unused)]
fn main() {
    pub trait Summary {
        fn summarize_author(&self) -> String;

        fn summarize(&self) -> String {
            // "({}さんの文章をもっと読む)"
            format!("(Read more from {}...)", self.summarize_author())
        }
    }

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

    impl Summary for Tweet {
        fn summarize_author(&self) -> String {
            format!("@{}", self.username)
        }
    }
}
  • 時として実装を見ないとわからないな

    このバージョンのSummaryを使用するために、型にトレイトを実装する際、実装する必要があるのはsummarize_authorだけです:

  • は?なんで?って思ったけども、summarizeはデフォルト実装があったわ

デフォルト実装を、そのメソッドをオーバーライドしている実装から呼び出すことはできないことに注意してください。

  • これは感覚的にわかるな

引数としてのトレイト

トレイトを定義し実装する方法はわかったので、トレイトを使っていろんな種類の型を受け付ける関数を定義する方法を学んでいきましょう。

  • ふむ、予想しているのとは違うかも

たとえば、Listing 10-13では、NewsArticleとTweet型にSummaryトレイトを実装しました。 ここで、引数のitemのsummarizeメソッドを呼ぶ関数notifyを定義することができます。ただし、引数itemはSummaryトレイトを実装しているような何らかの型であるとします。 このようなことをするためには、impl Trait構文を使うことができます。

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)
    }
}

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
  • 笑えるぐらい意味わからんくて草

引数のitemには、具体的な型の代わりに、implキーワードとトレイト名を指定します。 この引数は、指定されたトレイトを実装しているあらゆる型を受け付けます。

  • あーーーーーーーはいはい
  • これ前回言っていたトレイトを型として引数指定できるのかって話ね
  • チャレンジ(Java)でやった

impl Trait構文は単純なケースを解決しますが、実はより長いトレイト境界 (trait bound) と呼ばれる姿の糖衣構文 (syntax sugar) なのです。 それは以下のようなものです

  • syntax sugarやめろ、甘そうとかそれぐらいの感想しかでない
  • 意味わからんのでgemini time入る

gemini回答

Rustの「トレイト境界(Trait Bound)」、最初は少し戸惑いますよね。

簡単に言うと、これは**「この関数に渡すデータは、最低限これだけの『特技(機能)』を持っていてくれなきゃ困るよ!」**という、型に対する「条件(制約)」のことです。

たとえ話とコードの比較で整理してみましょう。

  1. 「何でもいい」は困る
    もし、関数が完全な「何でもいい(ジェネリックな型 T)」を受け取ってしまうと、関数の中で何もできなくなります。
// エラーになる例
fn notify<T>(item: &T) {
    println!("{}", item.summarize()); // エラー!Tがsummarizeできるか分からない
}

「どんな型でもいいよ」と言いつつ、中で summarize という特定のメソッドを使おうとすると、コンピュータは「数値だったらどうするの? summarize なんて持ってないよ!」と怒るわけです。
そこで、「Summary という特技(トレイト)を持っている型 T なら、何でもいいよ」という条件を付けます。これがトレイト境界です。

  • なるほど、ジェネリクスよりは自由度がないよーってやつね
  1. impl Trait と T: Trait の違い
  • いやそれな、geminiが続けて説明しようとしてるけど今日はここまで

まとめ

#
  • トレイト、interfaceがわかればほとんど知っている内容だった
  • が、syntax sugarみたいな訳わからん専門用語だされると急にウゴゴってなってしまう
  • おそらく明日はトレイト境界を深堀る
  • 次ここ