###### tags: `Rust` `Trait` `#[attr]` # Rust テキスト学習 5 # Trait トレイト = ある型が提供しなければならない機能をRustコンパイラに知らせる言語機能 要するに、他の言語で言うところのinterface機能 structのメソッドの実装の細かい内容を知らなくても traitに定義されている通りの引数、戻り値を知っていれば 同様に扱える。 いわゆる、ダックタイピングのための機能。 使い方は簡単で、 struct と impl の間に挟むだけ ```rust= struct Circle { x: f64, y: f64, radius: f64, } trait HasArea{ fn area(&self) -> f64; } trait Grow { fn grow(&mut self, g: f64); } impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } } impl Grow for Circle { fn grow(&mut self, g: f64){ self.radius += g; } } fn main() { let mut c = Circle{x: 1.0, y: 2.0, radius: 3.0}; c.grow(10.0); println!("{}", c.area()); } ``` trait ブロックと impl ブロックは似ているように見える。 しかし、traitブロックで定義するのはメソッドの型シグネチャ(識別子)だけ trait = 取り扱い、使い方の意 イメージとしては、 - trait : メソッドの return と args の型だけ定義 - impl : 実際の処理を実装 ## ジェネリックとトレイト ## ジェネリック関数におけるトレイト境界 Traitを用いるメリットの1つに、 **あるTypeの振る舞いを確約できる** という物がある これはジェネリックにおいて役に立つ機能である。 \<T>という型を受け取り、 .area() というメソッドを実行したいとき、 Tはどんな型も取りうるので、 .area() メソッドを実装しているとは限らない。 よってエラーになる。 **\<T: HasArea>** のように、ジェネリックTに HasArea トレイトのラベルをつけることで HasAreaに定義されるメソッドを実装した型のみを受け取れるようになる。 すなわち、 **ジェネリックパラメータの制限** を追加できるということである。 例. ```rust= trait HasArea { fn area(&self) -> f64; } struct Circle { x: f64, y: f64, radius: f64, } impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } struct Square { x: f64, y: f64, side: f64, } impl HasArea for Square { fn area(&self) -> f64 { self.side * self.side } } fn print_area<T: HasArea>(shape: T) { println!("This shape has an area of {}", shape.area()); } fn main() { let c = Circle { x: 0.0f64, y: 0.0f64, radius: 1.0f64, }; let s = Square { x: 0.0f64, y: 0.0f64, side: 1.0f64, }; print_area(c); print_area(s); } ``` 上記の例では、 Circle と Square という異なる型を print_area関数の 引数として実行している。 トレイト境界 = ジェネリックの引数にできるtypeか否か ## ジェネリック構造体のトレイト境界 ジェネリック構造体でも同様に trait による型制限ができる。 テキストでは、rectangle が正方形であるかを確認する is_square() メソッドを実装している。 ```rust= struct Rectangle<T> { x: T, y: T, width: T, height: T, } impl<T: PartialEq> Rectangle<T> { fn is_square(&self) -> bool { self.width == self.height } } ``` `core::cmp::PartialEq` : == 演算子で比較できることを保証するTrait。このTraitを実装していないtypeを == で比較するとエラー。 基本的なPrimitive型は実装していると思われるが、ジェネリックTを==比較するときは識別ラベルとしてつける必要あり。 ## Trait 実装ルール トレイトはあらゆる型に実装できる。 Primitive型に対しても例外ではない 例. i32 に HasArea トレイトを実装(impl) 可能だけれども、適切ではない トレイト実装には、手に負えなくなることを防止する2つのルールが有る 1. 自分のスコープ内で定義されていないトレイトは適用されない 2. 自分が実装する impl の対象の型は自分の手によって定義されなければならない ### 1. のルールについて 例えば、File 構造体には std によってWriteトレイトが提供されている。 しかしデフォルトではWriteで定義されるメソッド群を持っていない。 1のルールにより、自分の定義したTraitではないWriteは適用されていない。 File(struct) <- ~~Write(trait)~~ <- .write()(impl) Traitに対して実装されたimplすなわちメソッドを使うには、 Trait を useする必要がある。 **use** : あるstructに対するTraitを自分のスコープに適用するキーワード (他にも意味はありそうだが) ```rust= use std::io::Write; let mut f = std::fs::File::open("text.txt").expect("can't open"); let result = f.write(b"this is binary"); ``` useの文がないと、Writeトレイトが適用されていないので、トレイトに実装された .write()メソッドは使えない ### .2 のルールについて i32に対して、 自分で定義したHasArea トレイトは実装できる。 しかし、Rustによってi32に定義されているToStringトレイトを上書きして定義することはできない。 ## 複数トレイト境界 \<T> の横に、 `+` でつなげて複数定義できる。 , カンマとかではないので注意 ```rust= use std::fmt::Debug; fn foo<T: Clone + Debug>(x: T) { x.clone(); println!("{:?}", x); } ``` この関数のジェネリック引数とする型は、Clone と Debug 両方を実装していなければならない。 ## Where節 複数のトレイト境界を記述していくと、関数定義が長くなって邪魔 よって、 **where** キーワードを使って、トレイト境界の記述部分を 後ろの方に分けることができる。 where節の効果 - ジェネリックの境界記述を分ける = 見やすい - 改行できる = 関数定義の1行を短くする - 型ごとに異なる処理を実装 = ジェネリックトレイトとのあわせ技 ```rust= fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { // code } // 改行できる fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { // code } ``` ## ジェネリックトレイト struct や impl と同様に、traitにもジェネリックパラメータを付与できる。 where節によって、implで実装する関数の型パラメータごとに異なる処理を実装することも可能 ## デフォルトメソッド trait には最低でも関数の型を定義すればいい しかし、実際の処理を実装することもできる。 デフォルトメソッドはimplに実装を書かなくても良い その場合はデフォルトの処理が実行される。 ```rust= trait Foo { fn is_valid(&self) -> bool; fn is_invalid(&self) -> bool { !self.is_valid() } } ``` .is_invalid()メソッドは is_valid() メソッドの反対のbool値を返せば良いので 上記のようにかける。 ## 継承 tarit を定義するとき、他のtraitを継承できる `tarit Trait名 : 継承Trait名 {}` サブトレイトを適用すると、親トレイトも実装しないとエラー ## #[derive(trait)] derive = 派生する 標準ライブラリで提供されている、よく使うTraitは core:: で提供される Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd - Clone : 自身のコピーを作って返す .clone() メソッド - Copy : 代入演算子 = で、自身の値コピーを渡すようになる。move semantics を上書きするトレイト - Debug : println! マクロの {:?} のデバックフォーマットで表示する文字列を返すfmt() - Default : その型のデフォルト値を返すdefault()メソッド - PartialEq : 実装された型同士を == で比較する eq() , != で比較する ne() メソッド - Hash : [] でハッシュアクセスできるようになる - Ord : [] でオーダードアクセスできるようになる などなど、よく使うトレイトを自動的に実装してくれる。 その仕組みが、 **#[derive()]** アトリビュート ```rust= #[derive(Copy, Clone, Debug)] struct Person { name: String, age : i32, } ``` Personオブジェクトに、 - .clone() メソッド - 値コピー機能 - {:?}フォーマットでの出力文 が自動で実装された ### アトリビュートとは? 宣言を修飾する属性 2種類ある ```rust= #[attr] #![attr] ``` ! はアトリビュートが適用されるものを変更する - ! がつかない方: 次にあるアイテムに適用される - ! がつく方 : #![attr]を囲んでいるアイテムに適用される **例. #[test] アトリビュート** つけた関数がテスト関数になる テストのときだけ実行される。 ```rust= #[test] fn check1(){ assert_eq!(2, 1+1); } // 上のテスト関数と等価 fn check2(){ #![test] assert_eq!(2, 1+1); } ``` アトリビュートはRustが言語機能として提供しているもので ユーザーが独自のアトリビュートを作成することはサポートされていない ### アトリビュートの引数 ()で引数を追加できるものもある。 **例. #[cfg()] アトリビュート** ```rust= #[cfg(target_os = "macos")] mod macos_only {} ```