Rustのトレイト入門:実装方法を基礎から解説

スポンサーリンク

Rustのトレイト入門:実装方法を基礎から解説

はじめに

Rustを学んでいると trait という言葉が頻繁に出てきます。トレイトはRustの型システムの中心的な概念で、「型がどんな操作をサポートするか」を定義する仕組みです。

この記事ではトレイトの基本的な書き方と実装方法を解説します。


トレイトとは

トレイトは「メソッドのシグネチャの集まり」を定義するものです。他の言語のインターフェースに近い概念です。

trait Animal {
    fn name(&self) -> &str;
    fn sound(&self) -> &str;
}

このトレイトを実装した型は name()sound() を持つことが保証されます。


トレイトを実装する

impl トレイト名 for 型名 の形で実装します。

struct Dog;
struct Cat;

impl Animal for Dog {
    fn name(&self) -> &str {
        "犬"
    }
    fn sound(&self) -> &str {
        "ワン"
    }
}

impl Animal for Cat {
    fn name(&self) -> &str {
        "猫"
    }
    fn sound(&self) -> &str {
        "ニャー"
    }
}

実装した型は同じように使えます。

fn introduce(animal: &impl Animal) {
    println!("{}は{}と鳴きます", animal.name(), animal.sound());
}

introduce(&Dog);  // 犬はワンと鳴きます
introduce(&Cat);  // 猫はニャーと鳴きます

デフォルト実装

トレイト側にデフォルトの実装を持たせることができます。実装する型はオーバーライドしても、そのまま使ってもOKです。

trait Greet {
    fn name(&self) -> &str;

    fn hello(&self) {
        println!("こんにちは、{}です", self.name());
    }
}

struct User {
    name: String,
}

impl Greet for User {
    fn name(&self) -> &str {
        &self.name
    }
    // hello() はデフォルト実装をそのまま使う
}

let user = User { name: "太郎".to_string() };
user.hello();  // こんにちは、太郎です

トレイト境界:引数の型制約

関数の引数にトレイトを使って「このトレイトを実装している型なら何でも受け取る」という制約を書けます。

impl Trait 構文(シンプル)

fn print_name(animal: &impl Animal) {
    println!("{}", animal.name());
}

ジェネリクス + トレイト境界

fn print_name<T: Animal>(animal: &T) {
    println!("{}", animal.name());
}

複数のトレイトを要求する場合は + でつなぎます。

fn show<T: Animal + std::fmt::Debug>(animal: &T) {
    println!("{:?}", animal);
    println!("{}", animal.name());
}

where 句(複雑なときに読みやすい)

fn show<T>(animal: &T)
where
    T: Animal + std::fmt::Debug,
{
    println!("{:?}", animal);
}

戻り値にトレイトを使う

impl Trait は戻り値の型にも使えます。

fn make_animal() -> impl Animal {
    Dog
}

ただし返す型は1種類に固定されます。条件によって DogCat を返したい場合はトレイトオブジェクト(Box<dyn Animal>)を使います。

fn make_animal(is_dog: bool) -> Box<dyn Animal> {
    if is_dog {
        Box::new(Dog)
    } else {
        Box::new(Cat)
    }
}

まとめ

概念 書き方
トレイト定義 trait Name { fn method(&self); }
トレイト実装 impl Trait for Type { ... }
デフォルト実装 トレイト定義内にメソッド本体を書く
引数の制約 fn f(x: &impl Trait) / fn f<T: Trait>(x: &T)
動的ディスパッチ Box<dyn Trait>

Rustの標準ライブラリには便利なトレイトが多数用意されています。よく使うものは「Rustでよく使う標準トレイト一覧:Display・Debug・Clone・Iteratorなど」で解説しています。

RustのStringと&strの違いは「RustのStringと&strの違い:使い分けと変換方法まとめ」を参照してください。