Skip to content

Rust trait

foolmacky edited this page Apr 14, 2023 · 7 revisions

■ traitとはなにか

  • Rustにおいてポリモーフィズムを実現する機能
  • 型が持っている能力(何ができるか)を決めるのがtrait=メソッドシグニチャの集合体
  • ある型が「traitを実装している」=traitで指定されたメソッドをすべて実装されている

■ traitの実装

  • impl文で型に対してtraitを実装する
trait TraitName {
		fn sqrt(self) -> Self;
}
  • traitでのSelfはその関数を呼び出した型と同じ型がリターンすることを示す
  • traitの実装では定義されている関数はtraitの定義と完全に一致している必要があり、多くてもいけない
  • traitは継承できる
trait TraitName1TraitName2: TraitNameName1 + TraitNameName2 {}
  • trait内で宣言されたメソッドはお互いにcallできる
  • trait宣言内で実装されたデフォルトメソッドはオーバーライドできる
  • 複数のtraitで同じsyntaxの関数が実装されている場合は、traitを明示指定して呼び出す
struct Form {
    username: String,
    age: u8,
}

impl UsernameWidget for Form {
    fn get(&self) -> String {
        self.username.clone()
    }
}

impl AgeWidget for Form {
    fn get(&self) -> u8 {
        self.age
    }
}

fn main() {
    let form = Form {
        username: "rustacean".to_owned(),
        age: 28,
    };

    let username = <Form as UsernameWidget>::get(&form);
    let age = <Form as AgeWidget>::get(&form);
}
  • ジェネリックな型に対するtrait実装に条件を付与することができる
//Display traitを実装している型に対するToString traitの実装
impl<T: Display> ToString for T {
    // --snip--
}

■ traitの活用場面

  • ジェネリック関数
    • ジェネリック関数に引き渡す型が持つべき「抽象能力」を定義し、関数呼出で渡す引数の型がその能力を持っているなら、ジェネリック関数をその型に単相化できる
    • ジェネリック関数の引数の型について、whereで実装を必要とするtraitを指定。
    • 逆にそのtraitを実装していればジェネリック関数を使うことができる
    • traitの指定がないジェネリック関数は、あまり多くのことはできない
fn func_name<Number>(x: Number) -> Number
where Number: TraitName {
		//実装
}
  • traitの指定は複数でもOK
fn func_name<Number>(x: Number) -> Number
where Number: TraitName1 + TraitName2 {
		//実装
}
  • 外部で実装された型にメソッドを追加
    • 標準の型に直接固有のメソッドを加えることは許可されていないが、traitを使ってメソッドを結びつけることができる
trait HasLength {
		fn length(&self) -> usize
}
impl HasLength for str {
		fn length(&self) -> usize { self.chars().count() }
}
let s = "hoge";
print!("{} {}", s.len(), s.length());
  • マクロprint,println,formatでの標準traitの活用
    • マクロprintのプレースホルダー「{}」は標準ライブラリのstd::fmt::Displayトレイトのfmtメソッドを使っている
    • 「{:?}」はstd::fmt::Debugトレイトのfmtメソッドを使っている
    • 自作の型であっても、これらのtraitを実装することで、これらのマクロに指定できる
  • デストラクタ
    • Drop traitを定義することでdrop()関数が実装されるが、これがデストラクタになる
    • Box、Vec、String、File、Process等が実装して折り、独自の型に実装できる
  • イテレータ
    • Iterate traitは集合体を扱う場合にしばしば実装される
  • 関数の静的・動的ディスパッチ
fn main() {
    trait Draw {
        fn draw(&self);
    }

    struct Text {
        characters: String,
    }
    impl Text {
        fn from(text: &str) -> Text {
            Text {
                characters: text.to_string(),
            }
        }
    }
    impl Draw for Text {
        fn draw(&self) {
            print!("{}", self.characters);
        }
    }
    struct BoxedText {
        text: Text,
        first: char,
        last: char,
    }
    impl BoxedText {
        fn with_text_and_borders(text: &str, first: char, last: char) -> BoxedText {
            BoxedText {
                text: Text::from(text),
                first: first,
                last: last,
            }
        }
    }
    impl Draw for BoxedText {
        fn draw(&self) {
            print!("{}", self.first);
            self.text.draw();
            print!("{}", self.last);
        }
    }
    //静的ディスパッチ
    fn draw_text_static<T>(txt: &T)
    where
        T: Draw,
    {
        txt.draw();
    }
    //動的ディスパッチ
    fn draw_text_dynamic(txt: &dyn Draw) {
        txt.draw();
    }

    let greeting = Text::from("Hello");
    let boxed_greeting = BoxedText::with_text_and_borders("Hi", '[', ']');

    draw_text_static(&greeting);
    print!(",");
    draw_text_static(&boxed_greeting);

    draw_text_dynamic(&greeting);
    print!(",");
    draw_text_dynamic(&boxed_greeting);
}
  • [#derive]アトリビュートによるtupleに対する標準的なtraitの自動生成
    • 付与可能なtraitは限定
      • Eq, PartialEq, Ord, PartialOrd
      • Clone, Copy
      • Hash
      • Default
      • Debug
#[derive(PartialEq, PartialOrd)]
  • Box型を使ってtraitを関数のreturn値として実装
    • Rustの関数は直接はtraitを返すことはできないが、Boxでwrapすることで可能になる
fn random_animal(random_number: f64) -> Box<dyn Animal> {
    //Animal traitを実装したSheep or Cowをリターンする
    if random_number < 0.5 {
        Box::new(Sheep {})
    } else {
        Box::new(Cow {})
    }
}
  • core::opsのtraitをオーバーライドすることにより、演算子をオーバーライド
    • Rustの演算子の多くは、メソッドのシュガー構文なので、オーバーライド可能
impl ops::Add<Bar> for Foo {
    :
}
  • impl trait で関数の引数、返り値を指定する
    • 引数を指定
fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> {
    :
}
  • 返り値を指定。帰り値が一つのときのみ使える
fn combine_vecs(
    v: Vec<i32>,
    u: Vec<i32>,
) -> impl Iterator<Item=i32> {
    v.into_iter().chain(u.into_iter()).cycle()
}

Clone this wiki locally