【Rust】がばがばRust独学 - 11. Tests - 1 How to Write Tests
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回は、Testについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
How to Write Tests
公式ドキュメント(How to Write Tests - The Rust Programming Language)によると、テストは3つのアクションを持っているそうです。
- Set up any needed data or state.
- Run the code you want to test.
- Assert the results are what you expect.
必要なデータ・状態を設定し、テストコードを実行し、結果が期待通りで有ることをアサートします。
テスト関数の構造
テスト関数を実施するために、とりあえず新しいライブラリプロジェクトを作成しました。
$ cargo new demo-test --lib $ demo-test
テストコードについては、src/lib.rs
(training-rust/lib.rs at master · KentaHara/training-rust · GitHub)を参照ください。
まず、初期で記載されているテストを実行すると、正常動作が確認できました。
#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }
running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
別名関数を作成して実行すると、各々 tests::xxxxx
としてチェックされていることが確認できました。
#[test] fn exploration() { assert_eq!(2 + 2, 4); }
running 2 tests test tests::exploration ... ok test tests::it_works ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
panicを起こすと FAILED
となり、異常検知が出来ていることが確認できます。
#[test] fn another() { panic!("Make this test fail"); }
running 3 tests test tests::exploration ... ok test tests::it_works ... ok test tests::another ... FAILED failures: ---- tests::another stdout ---- thread 'tests::another' panicked at 'Make this test fail', src/lib.rs:24:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::another test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
assert!
マクロ利用によるテスト結果確認
デモコード(training-rust/lib.rs at master · KentaHara/training-rust · GitHub)のように、 Rectangle
の構造体および can_hold
関数を定義して、 assert!
マクロによるチェックを実施しました。 assert!
マクロは bool
によるチェックになります。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } // ... #[cfg(test)] mod tests { use super::*; // ... #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); } #[test] fn smaller_cannot_hold_larger() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(!smaller.can_hold(&larger)); } }
running 5 tests ... test tests::larger_can_hold_smaller ... ok test tests::smaller_cannot_hold_larger ... ok
assert_eq!
、 assert_ne!
によるテスト
it_adds_two
では 4 == 4
、it_adds_two_negative
では 5 == 4
としてテストを実施しました。
pub fn add_two(a: i32) -> i32 { a + 2 } // ... #[cfg(test)] mod tests { use super::*; // ... #[test] fn it_adds_two() { assert_eq!(4, add_two(2)); } #[test] fn it_adds_two_negative() { assert_ne!(5, add_two(2)); } }
running 7 tests ... test tests::larger_can_hold_smaller ... ok test tests::smaller_cannot_hold_larger ... ok
テスト失敗時のメッセージ変更
assert!
の第2引数と第3引数に対してフォーマットの利用が可能です。これは便利...
#[test] fn change_error_message() { let result = String::from("Hello Rust!"); assert!( result.contains("Hello New World!"), "result did not contain name, value was `{}`", result ); } #[test] fn change_error_message_use_eq() { let result = String::from("Hello Rust!"); assert_eq!( result, String::from("Hello New World!"), "result did not contain name, value was `{}`", result ); } #[test] fn change_error_message_use_ne() { let result = String::from("Hello Rust!"); assert_ne!( result, String::from("Hello Rust!"), "result did not contain name, value was `{}`", result ); }
running 8 tests ... test tests::change_error_message ... FAILED ... failures: ---- tests::change_error_message stdout ---- thread 'tests::change_error_message' panicked at 'result did not contain name, value was `Hello Rust!`', src/lib.rs:98:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::change_error_message test result: FAILED. 7 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
panicのチェック
シンプルにパニックを起こす関数 simple_panic
を定義して確認すると、 should_panic
である場合はpanicが見つけられなければ FAILED
となる。
pub fn simple_panic(flg: bool) -> bool { if flg { panic!("Panic!!"); } true } #[cfg(test)] mod tests { use super::*; // ... #[test] #[should_panic] fn should_panic_test_panic() { simple_panic(true); } #[test] #[should_panic] fn should_panic_test_nopanic() { simple_panic(false); } }
running 12 tests ... test tests::should_panic_test_nopanic ... FAILED test tests::should_panic_test_panic ... ok failures: ... ---- tests::should_panic_test_nopanic stdout ---- note: test did not panic as expected failures: tests::change_error_message tests::change_error_message_use_eq tests::change_error_message_use_ne tests::should_panic_test_nopanic test result: FAILED. 8 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out
上記の場合は、 note: test did not panic as expected
として注意書きがされる。
Result<T,E>
の利用
Resultを返り値として返すこともでき、Errの場合にはエラーメッセージを出すことも可能です。
#[test] fn it_works_use_result_ok() -> Result<(), String> { Ok(()) } #[test] fn it_works_use_result_err() -> Result<(), String> { Err(String::from("ERROR MESSAGE")) }
---- tests::it_works_use_result_err stdout ---- Error: "ERROR MESSAGE" thread 'tests::it_works_use_result_err' panicked at 'assertion failed: `(left == right)` left: `1`, right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', src/libtest/lib.rs:196:5
まとめ
mod
には#[cfg(test)]
を付与してtestということをconfigとして設定- テスト用の関数には
#[test]
を利用- 必ずpanicになるか否かを確認するためには
#[should_panic]
を利用
- 必ずpanicになるか否かを確認するためには
- 各種マクロによりテスト結果への対応可能
assert_eq!(a, b)
:a == b
assert_en!(a, b)
:a != b
assert!(x)
:x == true
- 各種マクロの引数を利用してテスト失敗時のメッセージを変更可能
Result<T,E>
による成否のチェックも可能
最後に
シンプルなテストの記述方法で書きやすそうですね。 Result<T,E>
を使える点がとてもありがたいです。
次回も引き続き、Testsに関して進めていきます。
【Rust】がばがばRust独学 - 10. Generic Types/Traits/Lifetimes - 3 Lifetimes
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、Generic Typesについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
Lifetimes
Rustの特徴的なownershipのLifetimesに関してになります。
Validating References with Lifetimes - The Rust Programming Language
ぶら下がり参照の防止
公式ドキュメントのコードより、下記を実行すると、 {}
ブロック内部で x
のスコープが外れます。
そのため、x
が開放されて参照先を無くした r
はエラーとなるべきですので、エラーとして
x
dropped here while still borrowed
となります。
fn main() { { let r; { let x = 5; r = &x; } println!("r: {}", r); } }
error[E0597]: `x` does not live long enough --> src/demo.rs:6:4 | 6 | r = &x; | ^^^^^^ borrowed value does not live long enough 7 | } | - `x` dropped here while still borrowed 8 | println!("r: {}", r); | - borrow later used here
実際のスコープは Validating References with Lifetimes - The Rust Programming Language
公式ドキュメントのボローチェッカー(Validating References with Lifetimes - The Rust Programming Language)がとても分かりやすいです。
関数の一般的な寿命
関数も同様に式(ブロック)内部でのみのLifetimeとなります。
そのため、公式ドキュメントのデモコードのように、longest(x: &str, y: &str) -> &str
とした場合、longest(string1.as_str(), string2)
における string1.as_str()
および string2
は
- どちらが選択されるか返り値(
-> &str
)から分からない &x
、&y
の具体的なLifetimeがlongest
関数から判断が付かない
であるため、コンパイラからの判断が出来ず、エラーとなります。
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
error[E0106]: missing lifetime specifier --> src/demo.rs:8:33 | 8 | fn longest(x: &str, y: &str) -> &str { | ^ expected lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
Lifetime Annotation Syntax
参照の有効期間を変更させないために、アノテーション '
を利用することが出来ます。
デモコードより、関数で利用する場合、 longest
関数において、 x
、 y
、 str
全てのライフタイムが同一でなければならない状態とした場合、 x
、 y
どちらが来たとしても、コンパイラから -> &str
のLifetimeは必ず x
、 y
と同様であることが分かるため、Lifetimeによるエラーはなくなります。
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
The longest string is abcd
また、下記のように異なるLifetime( longest(string1.as_str(), string2.as_str())
)を渡そうとした場合、 string1
は string2
よりの長いLifetimeであり、 result
のLifetimeは string2
と同一であるためエラーとはなりません。
fn main() { let string1 = String::from("long string is long"); { let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); println!("The longest string is {}", result); } } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
The longest string is long string is long
しかし、下記のように、 result
のLifetimeを string1
と同様にした場合、result
が string2
のLifetime外となってしまうパターンがあるため、エラーとなります。
fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result); } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
error[E0597]: `string2` does not live long enough --> src/demo.rs:6:38 | 6 | result = longest(string1.as_str(), string2.as_str()); | ^^^^^^^ borrowed value does not live long enough 7 | } | - `string2` dropped here while still borrowed 8 | println!("The longest string is {}", result); | ------ borrow later used here
Lifetime Elision
全てのリファレンスにはLifetimeが存在しますが、アノテーション無しでコンパイルできるパターンが存在します。
fn main() { let s = "Hello Rust!"; let fw = first_word(&s); println!("first word -> {}", fw); } fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
first word -> Hello
公式ドキュメント(Validating References with Lifetimes - The Rust Programming Language)で背景がしっかりと記載されていますが、ざっくりまとめると以下になります。
- Rustの初期バージョン(1.0以前)は対応していなかった
同一パターンでアノテーションしていたので、特定パターンを省略可能にした
- 省略できるが、完全な推論ではない
関数におけるLifetimeパラメータ名
- 引数:
input lifetime parameter
- 返り値:
output lifetime parameter
- 引数:
3つルールで推論できる場合、省略可能
- ルール1:
input lifetime parameter
が独自のLifetimeを持つ- 例1:
fn foo<'a>(x: &'a i32)
- 例2:
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
- 例1:
- ルール2:
input lifetime parameter
が1つだけの場合はoutput lifetime parameter
は同一Lifetimeになる- 例:
fn foo<'a>(x: &'a i32) -> &'a i32
- 例:
- ルール3:
input lifetime parameter
が複数の場合はそのうちの一つが&self
、&mut self
、 関数のどれかである場合
- ルール1:
上記のルールより、 first_word(s: &str) -> &str
はルール1、2を満たし、ルール3は適応外のため省略可能となります。
また、 impl
に対してもルール1、3を満たし、ルール2は適応外のため省略可能となります。
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention please: {}", announcement); self.part } }
The Static Lifetime
'static
は特別なLifetimeであり、Lifetimeはプログラムの全期間で存続可能です。
let s: &'static str = "I have a static lifetime.";
上記を利用を意識した場合はLifetimeが不要なのか確認する必要があります。長期間保持する必要があるのかどうか・・・
最後に
Lifetimeは省略系を除けば、Ownershipの深堀り的な内容ですね!
String
と usize
のLifetimeに気をつけたりすれば、かなり有効的に使えますね。
体に染み込ませるまでに時間がかかりそうではありますが、使いこなせるようになって、Rustのメリットを活かしたいですね。
次はテスト。DevOpsでも重要になってくるテストですね。
【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になります。