Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 10. Generic Types/Traits/Lifetimes - 2 Traits

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回も、Generic Typesについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Traits

Traits: Defining Shared Behavior - The Rust Programming Language

型にTraitsの実装

公式ドキュメントのデモコードでは src/lib.rs に対して実装されていましたが、簡略化のために src/main.rs に実装致します。

下記のように、 trait により関数の定義を実施し、 impl により実体の定義を NewsArticleTweet に付与することにより、 summarize を実行することが可能です。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

#[derive(Debug)]
struct Tweet {
    username: String,
    content: String,
    reply: bool,
    retweet: bool,
}

trait Summary {
    fn summarize(&self) -> String;
}

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

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
    let article = NewsArticle {
        headline: String::from("headline"),
        location: String::from("location"),
        author: String::from("author"),
        content: String::from("content"),
    };

    let tweet = Tweet {
        username: String::from("username"),
        content: String::from("content"),
        reply: false,
        retweet: true,
    };

    println!("article -> {:?}", article);
    println!("tweet -> {:?}", tweet);

    println!("article summary -> {}", article.summarize());
    println!("tweet summary -> {}", tweet.summarize());
}
article -> NewsArticle { headline: "headline", location: "location", author: "author", content: "content" }
tweet -> Tweet { username: "username", content: "content", reply: false, retweet: true }
article summary -> headline, by author (location)
tweet summary -> username: content

trait の初期実装

下記のように trait Summary に対して初期実装をし、impl Summary for NewsArticle することで、利用可能になります。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summary for NewsArticle {}

fn main() {
    let article = NewsArticle {
        headline: String::from("headline"),
        location: String::from("location"),
        author: String::from("author"),
        content: String::from("content"),
    };

    println!("article -> {:?}", article);

    println!("article summary -> {}", article.summarize());
}
article -> NewsArticle { headline: "headline", location: "location", author: "author", content: "content" }
article summary -> (Read more...)

また、初期実装については、 impl Summary for NewsArticle により上書きが可能です。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

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

fn main() {
    let article = NewsArticle {
        headline: String::from("headline"),
        location: String::from("location"),
        author: String::from("author"),
        content: String::from("content"),
    };

    println!("article -> {:?}", article);

    println!("article summary -> {}", article.summarize());
}
article -> NewsArticle { headline: "headline", location: "location", author: "author", content: "content" }
article summary -> headline, by author (location)

上書きや初期実装は、一部だけ上書き・初期実装をすることが可能です。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summary for NewsArticle {
    fn summarize_author(&self) -> String {
        format!("@{}", self.author)
    }
}

fn main() {
    let article = NewsArticle {
        headline: String::from("headline"),
        location: String::from("location"),
        author: String::from("author"),
        content: String::from("content"),
    };

    println!("article -> {:?}", article);

    println!("article summary -> {}", article.summarize());
    println!("article author -> {}", article.summarize_author());
}
article -> NewsArticle { headline: "headline", location: "location", author: "author", content: "content" }
article summary -> (Read more...)
article author -> @author

パラメータとしての特性

Summary を利用して、 fn notify(item: impl Summary)item を定義することにより、利用可能です。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summary for NewsArticle {}

fn notify(item: impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

fn main() {
    let article = NewsArticle {
        headline: String::from("headline"),
        location: String::from("location"),
        author: String::from("author"),
        content: String::from("content"),
    };

    println!("article -> {:?}", article);
    notify(article);
}
article -> NewsArticle { headline: "headline", location: "location", author: "author", content: "content" }
Breaking news! (Read more...)

Generic型による利用

fn notify<T: Summary>(item: T) とすることにより、 Summary のTraitを利用することが可能になります。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summary for NewsArticle {}

fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}

fn main() {
    let article = NewsArticle {
        headline: String::from("headline"),
        location: String::from("location"),
        author: String::from("author"),
        content: String::from("content"),
    };

    println!("article -> {:?}", article);
    notify(article);
}
article -> NewsArticle { headline: "headline", location: "location", author: "author", content: "content" }
Breaking news! (Read more...)

上記は単一引数であるためメリットが薄いですが、例えば

pub fn notify(item1: impl Summary, item2: impl Summary) {

とするよりも、

pub fn notify<T: Summary>(item1: T, item2: T) {

として、両方のパラメータを縛ることが可能になり、見通しも良くなります。

+ による複数の特性境界の指定

前回記事で指定していたように、 largest<T: PartialOrd + Copy> により複数の特性境界をもたせることが可能です。

where による特性境界の指定

例えば、前回記事で作成した largest 関数を2つ同時に求めるような largests を作成したとします。(実装的に実運用的ではないですが)

fn main() {
    let list1: Vec<usize> = vec![1, 2, 3, 99, 5];
    let list2: Vec<char> = vec!['a', 'b', 'z', 'd'];

    let result = largests(&list1, &list2);
    println!("largests -> {:#?}", result);
}

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest: T = list[0];
    for &number in list.iter() {
        if number > largest {
            largest = number;
        }
    }
    largest
}

fn largests<T: PartialOrd + Copy, U: PartialOrd + Copy>(list1: &[T], list2: &[U]) -> (T, U) {
    let largest1 = largest(list1);
    let largest2 = largest(list2);
    (largest1, largest2)
}
largests -> (
    99,
    'z',
)

この時、下記の見通しがとても悪くなります。

fn largests<T: PartialOrd + Copy, U: PartialOrd + Copy>(list1: &[T], list2: &[U]) -> (T, U)

そのため、 where を利用することにより、

fn largests<T, U>(list1: &[T], list2: &[U]) -> (T, U)
where
    T: PartialOrd + Copy,
    U: PartialOrd + Copy,
{

とすることができ、見通しが良くなります。

fn main() {
    let list1: Vec<usize> = vec![1, 2, 3, 99, 5];
    let list2: Vec<char> = vec!['a', 'b', 'z', 'd'];

    let result = largests(&list1, &list2);
    println!("largests -> {:#?}", result);
}

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest: T = list[0];
    for &number in list.iter() {
        if number > largest {
            largest = number;
        }
    }
    largest
}

fn largests<T, U>(list1: &[T], list2: &[U]) -> (T, U)
where
    T: PartialOrd + Copy,
    U: PartialOrd + Copy,
{
    let largest1 = largest(list1);
    let largest2 = largest(list2);
    (largest1, largest2)
}
largests -> (
    99,
    'z',
)

impl による返り値の方指定

fn return_summary() -> impl Summary とすることにより、 NewsArticle を返すことが可能です。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

trait Summary {
    fn summarize(&self) -> String;
}

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

fn main() {
    let article = return_summary();

    // `impl Summary` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
    // println!("article -> {:?}", article);

    println!("article summary -> {}", article.summarize());
}

fn return_summary() -> impl Summary {
    NewsArticle {
        headline: String::from("headline"),
        location: String::from("location"),
        author: String::from("author"),
        content: String::from("content"),
    }
}

ただし、 impl Summary は単一型を返すことしか許容されて院内ため、 Tweet を返すことはできません。異なる型を返す場合は、公式ドキュメントのChapter 17に記されているそうです。

#[derive(Debug)]
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

#[derive(Debug)]
struct Tweet {
    username: String,
    content: String,
    reply: bool,
    retweet: bool,
}

trait Summary {
    fn summarize(&self) -> String;
}

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

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
    let article = return_summary(true);
    let tweet = return_summary(false);
    println!("article summary -> {}", article.summarize());
    println!("tweet summary -> {}", tweet.summarize());
}

fn return_summary(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from("headline"),
            location: String::from("location"),
            author: String::from("author"),
            content: String::from("content"),
        }
    } else {
        Tweet {
            username: String::from("username"),
            content: String::from("content"),
            reply: false,
            retweet: true,
        }
    }
}
error[E0308]: if and else have incompatible types
  --> src/demo.rs:60:3
   |
52 |         if switch {
   |    _____-
53 |   |         NewsArticle {
   |  _|_________-
54 | | |             headline: String::from("headline"),
55 | | |             location: String::from("location"),
56 | | |             author: String::from("author"),
57 | | |             content: String::from("content"),
58 | | |         }
   | |_|_________- expected because of this
59 |   |     } else {
60 |   |         Tweet {
   |  _|_________^
61 | | |             username: String::from("username"),
62 | | |             content: String::from("content"),
63 | | |             reply: false,
64 | | |             retweet: true,
65 | | |         }
   | |_|_________^ expected struct `NewsArticle`, found struct `Tweet`
66 |   |     }
   |   |_____- if and else have incompatible types
   |
   = note: expected type `NewsArticle`
              found type `Tweet`

impl への特性境界の利用

公式のデモコードを利用して、 impl<T: Display + PartialOrd> Pair<T> のように特性境界を与えることが可能です。

use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

fn main() {
    let p1 = Pair { x: 1, y: 0 };
    let p2 = Pair { x: 0, y: 1 };

    p1.cmp_display();
    p2.cmp_display();
}
The largest member is x = 1
The largest member is y = 1

最後に

trait の定義を見ていきました。

公式ドキュメントにも記載されているように、interfacesとして考えると飲み込みやすいですね。

Note: Traits are similar to a feature often called interfaces in other languages, although with some differences.

また、 impl A for BB に対して A による実装ができるのは本当にありがたいです。まれにですが、特定の型に対して関数をつけたい場合があるので。

CloneCopy などがどのように実装されているか、traitimpl が分かればかなり読めるものになりますね。 charaken.hatenablog.com

次回は引き続き、Generic Typesで、Lifetimesになります。