基本トレイト
概要
Rustの型(struct, enum)は継承が不可。 メソッドは実質関数に過ぎない。
ある特定の振る舞いの抽象化はトレイトを通して実現する。 (この点からRustの型はHaskellのdata型、トレイトは型クラスに対応すると考えることが出来る。)
標準スタンダードライブラリではすでに共通の処理がまとめられた基本的なトレイトが定義されている。
Rustで開発を行うにあたってこれらの基本トレイトの理解は欠かせない。
基本トレイト
ToString
型が文字列に変換できることを表すトレイト。
to_string()
メソッドを持つ
// ジェネリックスを使った宣言 fn to_string1<T: ToString>(item: &T) -> String { item.to_string() } // impl トレイトを使った宣言。`to_string1`メソッドの糖衣構文となる fn to_string2(item: &impl ToString) -> String { item.to_string() } // トレイトオブジェクトを使った宣言 fn to_string3(item: &dyn ToString) -> String { item.to_string() }
Display
{}
フォーマット出力が出来ることを表す{:#}
デフォルトのフォーマット出力が出来ることを表す- ユーザーに対するメッセージを表示する
ToString
トレイトを自動実装するto_string()
- deriving 不可
struct Point { x: i32, y: i32, } impl std::fmt::Display for Point { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "({} {})", self.x, self.y) } } let p = Point { x: 10, y: 10 }; println!("{}", p);
Debug
Displayトレイトと似ているが、型をデバッグ出力することが出来ることを表すトレイト。
{:?}
フォーマット出力が出来ることを表す。簡易表示。{:#?}
フォーマット出力が出来ることを表す。- deriving 可
use std::fmt; struct Position { latitude: f32, longitude: f32, } impl fmt::Debug for Position { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Position") .field("latitude", &self.latitude) .field("longitude", &self.longitude) .finish() } /* fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.latitude, self.longitude) } */ } let p = Position { latitude: 1.987, longitude: 43.7 }; println!("{:?}", p); println!("{:#?}", p); /* 表示結果 Position { latitude: 1.987, longitude: 43.7 } Position { latitude: 1.987, longitude: 43.7, } */
Default
デフォルト値を持つ型を表すトレイト。
Default::default()
によって生成することが可能になる- derive可
#[derive(Default)] struct MyStruct { name: String, age: u16, } let mine: MyStruct = Default::default();
Configurationのような設定値をたくさん持つ型の場合、Defaultトレイトを実装することで初期化の短縮が可能となる。
init_config(Config { job_name: "test", output_dir: Path::new("/temp"), ..Default::default() })
From / Into
pub trait From<T> { fn from(T) -> Self; }
From
トレイトは form()
メソッドを通じてある異なった型から生成出来ることを表すトレイト。
Into
トレイトは From
トレイトのペアとなるトレイトで、From
トレイトを実装すると自動で導出される。
Stringの生成 from 'str
impl <'_> From<&'_ str> for String
String型はstr型から生成することが出来る
// convert String from &str let s = String::from("hello");
これによってInto<String>
も自動で実装され、&str
から into()
メソッドを通して String
に変換することが出来る。
// convert &str into String let string = "hello".into();
エラー処理
From
トレイトとInto
トレイトのペアを使って既存のエラーをラッピングするカスタムのエラー型を定義することが出来る。
enum CliError { IoError(io::Error), ParseError(num::ParseIntError), } impl From<io::Error> for CliError { fn from(error: io::Error) -> Self { CliError::IoError(error) } } impl From<num::ParseIntError> for CliError { fn from(error: num::ParseIntError) -> Self { CliError::ParseError(error) } } // 自動でIntoトレイトが実装されるので各エラーから変換してくれる fn open_and_parse_file(file_name: &str) -> Result<i32, CliError> { // io::Errorから let mut contents = fs::read_to_string(&file_name)?; // ParseIntErrorから let num: i32 = contents.trim().parse()?; }
Clone / Copy
Clone
トレイトは、新たに同一のオブジェクトを生成することが出来ることを表すトレイト。
clone()
メソッドを呼び出すと値がコピーされる
#[derive(Clone)] struct Person { f_name: String, l_name: String, } let p1 = Person { f_name: "Tanaka".into(), l_name: "Daisuke".into(), }; let p2 = p1.clone();
Copy
トレイトは、コピーセマンティクスを持つ型であることを表すトレイト。
- structは、基本ムーブセマンティクス
- マーカートレイトなので、メソッドを持たない
Clone
トレイトのサブトレイトなので、Clone
トレイトも実装する必要がある
#[derive(Debug, Clone, Copy)] struct Point { x: i32, y: i32, } let p1 = Point { x: 5, y: 10 }; let p2 = p1; // OK println!("{:?}", p1);
すべてがCopy
トレイトになれるわけではなく、各フィールド値がCopy
トレイトを実装されている必要がある。
#[derive(Debug, Clone, Copy)] struct PointList { points: Vec<Point>, } 9 | #[derive(Debug, Clone, Copy)] | ^^^^ 10 | struct PointList { 11 | points: Vec<Point>, | ------------------ this field does not implement `Copy` |
FromStr
pub trait FromStr { type Err; fn from_str(s: &str) -> Result<Self, Self::Err>; }
リテラル文字列 &str
から変換できることを表すトレイト。
str::parse()
メソッドで暗黙的に使用されている
impl FromStr for Point { type Err = ParseIntError; // どちらでも良い // fn from_str(s: &str) -> Result<Point, ParseIntError> { fn from_str(s: &str) -> Result<Self, Self::Err> { let coords: Vec<&str> = s.trim_matches(|p| p == '(' || p == ')') .split(',') .collect(); let x = coords[0].parse::<i32>()?; let y = coords[1].parse::<i32>()?; Ok(Point { x: x, y: y }) } } let _p = Point::from_str("(1,2)");
AsRef<T: ?Sized>
pub trait AsRef<T> where T: ?Sized, { fn as_ref(&self) -> &T; }
型Tへの参照を持つことを表すトレイト。
AsRef
トレイトを使うことで、Path
は 文字列リテラルやStringなどから生成することが出来る。
fn exists(p: impl AsRef<Path>) -> bool { // as_ref()メソッドで&Pathを取り出し p.as_ref().exists() } // &str exists("asref.rs"); // String exists(String::from("asref.rs")); // Path extsts(PathBuf::from("asref.rs"));
文字列リテラル、Stringどちらも取得出来るようにする
// fn is_hello<T: AsRef<str>(s: T) と同一 fn is_hello(s: impl AsRef<str>) { assert_eq!("hello", s.as_ref()); } is_hello("hello"); is_hello(String::from("hello");
Deref
イミュータブルな参照外し演算子(*
)が使用できることを表すトレイト。
let mut v = vec![100, 32, 57]; for i in &mut v { // NG i += 50; } println!("v = {:?}", v); 4 | i += 50; | -^^^^^^ | | | cannot use `+=` on type `&mut {integer}` | help: `+=` can be used on '{integer}', you can dereference `i` | 4 | *i += 50; | ^^
正しくは以下。
let mut v = vec![100, 32, 57]; for i in &mut v { // 参照外し iの型は&i32 *i += 50; } println!("v = {:?}", v);
MyBox<&T>
struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } let m = MyBox::new(5); // 内部では *(m.dref()) が呼び出されている assert_eq!(5, *m); // 内部では m.dref() が呼び出されている let x: &i32 = &m; // 型注釈を付けなければ参照になるだけ let y = &m; // &MyBox<i32>
Derefの意図
Deref coercion
StringはDerefトレイトを実装していて、&strに変換出来る。
let s1 = String::from("Hello"); let s2: &str = &s1;
let s1 = String::from("Hello"); let m = MyBox::new(s1); let s2: &str = &m; // 自動的に&strまで変換される
Borrow
HashSet<String>
において、該当のStringが含まれているかどうか確認するたびにStringを生成する必要が発生する。
そこでBorrowトレイトを通すことで貸与型を受け取ることを宣言することができる。
impl<K, V> HashMap<K, V> { pub fn get<Q>(&self, k: &Q) -> Option<&V> where K: Borrow<Q>, Q: Hash + Eq + ?Sized { // ... } }
StringはBorrowトレイトを実装している
impl Borrow<str> for String { fn borrow(&self) -> &str }
let mut set = HashSet::new(); // set is now HashSet<String> set.insert("one".to_string()); if set.contains("two") { // containsはBorrowを引き受ける println!("got two"); }
Read / Write
Read
トレイトを実装している型として
std::fs::File
, std::io::Stdin
が存在する。
- デフォルトではバッファーされない
lock
することでバッファーされるようになり、最速で読み込みされる
let stdin = io::stdin(); let mut lockin = stdin.lock(); // lockin is buffered!
書き込みのためには、Write
トレイトがある。
socketや標準ストリームstdout,stderrがこれを実装する。
これらもバッファーされない。そして io::BufWrite
が存在し、Write
を実装する型はバッファリングの追加を避けられる。
Iterator / IntoIterator
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>;
Iteratorトレイトを実装するとforステートメントにわたすことが出来る。
for n in [10, 20, 30].iter() { println!("got {}", n); }
イテレーターに変換できることを表すものとして IntoIterator
がある。
for n in &[10, 20, 30] {}
と書くことが出来る。