Rust Trait を使った継承のような実装方法まとめ

Rust Trait を使った継承のような実装方法まとめ

Rust で Trait を使った継承のような実装

Rust は、Trait を使って struct に関数の実装を強制することができます。Trait はオブジェクト指向のプログラミング言語でいうところの、インターフェースや抽象クラスのような役割を果たします。

Trait 含め、struct に関数を実装する方法を忘れないようにメモします。

struct は関数をメンバに持たない

Rust の struct(構造体)は、関数の実装を持たず、メンバのみが定義されます。

struct Human {
    pub age: u8,
}

上の Human struct は、age(年齢)がメンバとして定義されます。pub をつけているので外部に公開されるメンバです。

impl で struct に関数を実装する

Rust の struct は、impl を使って関数を実装することが可能です。

struct Human {
    pub age: u8,
}

impl Human {
    fn say_age(&mut self) {
        println!("{}歳です。", self.age);
    }
}

fn main() {
    let mut human = Human {
        age: 30,
    };
    human.say_age(); // 30歳です。
}

say_age() 関数を実装して、年齢を出力しています。

trait で実装を強制する

Trait は、ある型が実装しなければならない機能をコンパイラに伝える機能を持ちます。C# や Java でいうところのインターフェースの役割です。

例えば人間は哺乳類であるという関係を Trait を使って表現すると以下のようになります。

struct Human {}

trait Mammals {
    fn do_mammals_work(&mut self);
}

impl Mammals for Human {
    fn do_mammals_work(&mut self) {
        println!("人間は哺乳類です。");
    }
}

fn main() {
    let mut human = Human {};
    human.do_mammals_work();
}

trait は、実装すべき関数のシグネチャを定義します。あとは impl で関数を実装するのと同じようにします。impl Mammals for Human では、Human に対して Mammanls を実装るように要請しています。以下、シグネチャ通りの関数を実装することでコンパイルできるようになります。

Human struct がMammls trait を実装ることをコンパイラが知っているため、オブジェクトが定義されたメソッドを呼び出せるようになります。

C# や Java のインターフェースとの違いは実装を定義できる点と、メンバについては規定できない点にあります。例えば、trait の定義でシグネチャではなく実装まで行うと、その関数が呼び出せるようになります。

struct Human {}

trait Mammals {
    // fn do_mammals_work(&mut self);
    fn do_mammals_work(&mut self) {
        println!("哺乳類です。");
    }
}

impl Mammals for Human {
}

fn main() {
    let mut human = Human {};
    human.do_mammals_work();
}

上記のような定義でもコンパイルが通ります。impl で do_mammals_work を実装すると、それが優先して呼ばれるようになります。

ここでは自分で定義した struct に対して trait で実装をしていますが、あらゆる型に対して trait は有効になります。既存の型である char を拡張して次のような実装もできます。

trait MyFavorite {
    fn my_favorite(self) -> bool;
}

impl MyFavorite for char {
    fn my_favorite(self) -> bool {
        self == 'A'
    }
}

fn main() {
    println!("{}", 'A'.my_favorite()); // true
    println!("{}", 'x'.my_favorite()); // false
}

この既存機能に対する拡張は非常に強力ですが、対象の Trait がスコープ内になければ利用できません。したがって別モジュールなどで定義されている場合は、use しなければ影響を受けないようになっています。

Trait を継承した Trait (サブトレイト)

例えば哺乳類は動物なので Mammals トレイトを実装する際に、Animal トレイトも必ず実装してほしいような場合を考えます。このような場合にはトレイトを継承?させます。

サブトレイトというらしいですが検索してもあまり出てこず…

trait Animal {
    fn do_animal_work(&mut self);
}

trait Mammals: Animal {
    fn do_mammals_work(&mut self);
}

struct Human {}

impl Animal for Human {
    fn do_animal_work(&mut self) {
        println!("人間は動物です。");
    }
}

impl Mammals for Human {
    fn do_mammals_work(&mut self) {
        println!("人間は哺乳類です。");
    }
}

fn main() {
    let mut human = Human {};
    human.do_mammals_work();
    human.do_animal_work();
}

trait Mammals: Animal {} とすることで、Mammals を実装するすべての型に対して、Animal trait の実装を強制します。こうすることでもし、Animal trait の実装を忘れると、コンパイラがエラーを吐きます。

error[E0277]: the trait bound `Human: Animal` is not satisfied
  --> src/main.rs:18:6
   |
18 | impl Mammals for Human {
   |      ^^^^^^^ the trait `Animal` is not implemented for `Human`

error: aborting due to previous error

Animal を 実装しないと上記のようなエラーになります。

以上。

Rustカテゴリの最新記事