RustにおけるTraitの解説：基本から応用まで

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/f37dce6c-a785-40da-9189-57d27b761f3e.png)](https://leapcell.io/?lc_t=q_rusttrait)

## trait とは

Rust において、trait は共有の振る舞いを定義する方法です。trait を使うことで、特定の型が実装しなければならないメソッドを指定でき、ポリモーフィズムやインターフェースの抽象化を実現できます。

以下に、`Printable` という名前の trait を定義する簡単な例を示します。この trait には `print` というメソッドが含まれています。

```rust
trait Printable {
    fn print(&self);
}
```

## trait の定義と実装

trait を定義するには、`trait` キーワードを使用し、その後に trait 名と中括弧 `{}` を続けます。中括弧内に、この trait に含めるメソッドを定義します。

trait を実装するには、`impl` キーワードを使用し、続けて trait 名と `for` キーワードを記述し、その後に trait を実装する型を指定します。中括弧 `{}` の中で、その型に対して trait に含まれるすべてのメソッドを実装する必要があります。

以下の例では、先ほど定義した `Printable` trait を `i32` 型に対して実装しています。

```rust
impl Printable for i32 {
    fn print(&self) {
        println!("{}", self);
    }
}
```

この例では、`i32` 型に対して `Printable` trait を実装し、`print` メソッドのシンプルな実装を提供しています。

## trait の継承と合成

Rust では、既存の trait を拡張するために、継承や合成を使用できます。継承を使うと、親 trait に定義されたメソッドを新しい trait で再利用できます。一方、合成を使うと、複数の異なる trait を新しい trait に組み合わせることができます。

以下は、継承を使って `Printable` trait を拡張する方法を示した例です。

```rust
trait PrintableWithLabel: Printable {
    fn print_with_label(&self, label: &str) {
        print!("{}: ", label);
        self.print();
    }
}
```

この例では、新しい `PrintableWithLabel` trait を定義し、`Printable` trait を継承しています。つまり、`PrintableWithLabel` を実装する型は、必ず `Printable` trait も実装しなければなりません。さらに、`PrintableWithLabel` には `print_with_label` という新しいメソッドが定義されており、値を出力する前にラベルを表示できます。

次に、合成を使用して新しい trait を定義する例を示します。

```rust
trait DisplayAndDebug: Display + Debug {}
```

この例では、新しい `DisplayAndDebug` trait を定義し、標準ライブラリの `Display` と `Debug` trait を組み合わせています。したがって、`DisplayAndDebug` trait を実装する型は、`Display` と `Debug` の両方を実装している必要があります。

## trait をパラメータや戻り値として使用する

Rust では、関数のシグネチャ内で trait をパラメータや戻り値として使用できます。これにより、より汎用的で柔軟なコードを記述できます。

以下に、先ほど定義した `PrintableWithLabel` trait を関数のパラメータとして使用する例を示します。

```rust
fn print_twice<T: PrintableWithLabel>(value: T) {
    value.print_with_label("First");
    value.print_with_label("Second");
}
```

この例では、`print_twice` という関数を定義し、汎用型 `T` を引数として受け取ります。この `T` は `PrintableWithLabel` trait を実装している必要があります。そして、関数内では `value` の `print_with_label` メソッドを 2 回呼び出しています。

次に、trait を関数の戻り値として使用する例を示します。

```rust
fn get_printable() -> impl Printable {
    42
}
```

しかし、`fn get_printable() -> impl Printable { 42 }` というコードは正しくありません。なぜなら `42` は整数型であり、`Printable` trait を実装していないためです。

正しい方法としては、`Printable` trait を実装した型を戻り値として返すことです。例えば、`i32` 型に対して `Printable` trait を実装すれば、次のように記述できます。

```rust
impl Printable for i32 {
    fn print(&self) {
        println!("{}", self);
    }
}

fn get_printable() -> impl Printable {
    42
}
```

この例では、`i32` 型に `Printable` trait を実装し、シンプルな `print` メソッドを定義しています。そして、`get_printable` 関数は `i32` 型の値 `42` を返します。`i32` は `Printable` trait を実装しているため、このコードは正しく動作します。

## trait オブジェクトと静的ディスパッチ

Rust では、ポリモーフィズムを実現するために、静的ディスパッチと動的ディスパッチの 2 つの方法を使用できます。

### 静的ディスパッチ

静的ディスパッチは、ジェネリクスを使用することで実現されます。ジェネリクスを使うと、コンパイラはそれぞれの型ごとに個別のコードを生成します。これにより、どのメソッドを呼び出すかがコンパイル時に決定されます。

### 動的ディスパッチ

一方、動的ディスパッチは trait オブジェクトを使用することで実現されます。trait オブジェクトを使用すると、コンパイラは汎用的なコードを生成し、どのメソッドを呼び出すかは実行時に決定されます。

以下に、静的ディスパッチと動的ディスパッチを使用してポリモーフィズムを実現する例を示します。

```rust
fn print_static<T: Printable>(value: T) {
    value.print();
}

fn print_dynamic(value: &dyn Printable) {
    value.print();
}
```

この例では、2 つの関数 `print_static` と `print_dynamic` を定義しています。

- `print_static` 関数はジェネリクス `T` を使用しており、`T` は `Printable` trait を実装している必要があります。この関数を呼び出すと、コンパイラはそれぞれの型に対して個別のコードを生成します（静的ディスパッチ）。
- `print_dynamic` 関数は `&dyn Printable` という trait オブジェクトを受け取ります。この場合、コンパイラは 1 つの汎用的なコードを生成し、実行時にどのメソッドを呼び出すかを決定します（動的ディスパッチ）。

## 関連型とジェネリック制約

Rust では、より複雑な trait を定義するために、関連型やジェネリック制約を使用できます。

### 関連型（Associated Types）

関連型を使用すると、trait 内で他の型と関連付けられる型を定義できます。これにより、trait 内で特定の型に依存するメソッドを定義することが可能になります。

以下に、関連型を使用して `Add` という trait を定義する例を示します。

```rust
trait Add<RHS = Self> {
    type Output;

    fn add(self, rhs: RHS) -> Self::Output;
}
```

この例では、`Add` trait を定義し、次のような要素を含んでいます。

- `Output` という関連型を定義
- `add` というメソッドを定義し、`RHS` というジェネリック型の引数を受け取る（デフォルトは `Self`）
- `add` メソッドは `Self::Output` 型の値を返す

このように、関連型を使用すると、より柔軟な trait の設計が可能になります。

### ジェネリック制約（Generic Bounds）

ジェネリック制約を使用すると、ジェネリック型が満たすべき条件を指定できます。例えば、「あるジェネリック型は特定の trait を実装していなければならない」といった制約を加えることができます。

以下に、ジェネリック制約を使用して `SummableIterator` という trait を定義する例を示します。

```rust
use std::iter::Sum;

trait SummableIterator: Iterator
where
    Self::Item: Sum,
{
    fn sum(self) -> Self::Item {
        self.fold(Self::Item::zero(), |acc, x| acc + x)
    }
}
```

この例では、以下のように `SummableIterator` trait を定義しています。

- `Iterator` trait を継承
- `Self::Item` に `Sum` trait を実装していることを要求（ジェネリック制約）
- `sum` メソッドを定義し、イテレータのすべての要素を合計する

ジェネリック制約を使うことで、型の安全性を維持しながら、より柔軟な trait の設計が可能になります。

## 実例: trait を使用したポリモーフィズムの実装

以下の例では、先ほど定義した `PrintableWithLabel` trait を使用してポリモーフィズムを実装する方法を示します。

```rust
struct Circle {
    radius: f64,
}

impl Printable for Circle {
    fn print(&self) {
        println!("Circle with radius {}", self.radius);
    }
}

impl PrintableWithLabel for Circle {}

struct Square {
    side: f64,
}

impl Printable for Square {
    fn print(&self) {
        println!("Square with side {}", self.side);
    }
}

impl PrintableWithLabel for Square {}

fn main() {
    let shapes: Vec<Box<dyn PrintableWithLabel>> = vec![
        Box::new(Circle { radius: 1.0 }),
        Box::new(Square { side: 2.0 }),
    ];

    for shape in shapes {
        shape.print_with_label("Shape");
    }
}
```

### この例のポイント

1. `Circle` と `Square` という 2 つの構造体を定義
2. `Printable` と `PrintableWithLabel` trait をそれぞれの構造体に実装
3. `main` 関数で `Vec<Box<dyn PrintableWithLabel>>` を作成し、異なる型のオブジェクトを格納
4. `print_with_label` メソッドを呼び出し、ラベル付きで各図形を出力

このコードでは、`Circle` と `Square` の 2 種類の図形を `PrintableWithLabel` trait オブジェクトとして `Vec` に格納し、それらをループ処理して `print_with_label` を呼び出しています。これにより、異なる型のオブジェクトを一元的に管理し、ポリモーフィズムを実現しています。

本記事では、Rust における `trait` の基本概念と応用について解説しました。`trait` は Rust における重要な概念であり、ポリモーフィズムやコードの再利用を促進する強力なツールです。ぜひ実際にコードを書きながら、その便利さを体験してみてください。

---

### [私たちはLeapcell、Rustプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_rusttrait)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/fafcfdca-a008-4500-a139-be8c658a423f.png)](https://leapcell.io/?lc_t=q_rusttrait)

[Leapcell](https://leapcell.io/?lc_t=q_rusttrait)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/8812bb46-a1f5-415c-896c-9781dcd90dce.png)](https://leapcell.io/?lc_t=q_rusttrait)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/deep-dive-into-rust-traits)