基本トレイト

概要

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] {} 

と書くことが出来る。

参照

stevedonovan.github.io

RustのDerefトレイトによる挙動を確認してみた - Qiita