【Rust】がばがばRust独学 - 10. Generic Types/Traits/Lifetimes - 2 Traits
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、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
により実体の定義を NewsArticle
や Tweet
に付与することにより、 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 B
で B
に対して A
による実装ができるのは本当にありがたいです。まれにですが、特定の型に対して関数をつけたい場合があるので。
Clone
や Copy
などがどのように実装されているか、trait
や impl
が分かればかなり読めるものになりますね。
charaken.hatenablog.com
次回は引き続き、Generic Typesで、Lifetimesになります。