###### 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 {}
```