【Effective-Rustlings-jp】Day 11：ジェネリクスとトレイトオブジェクトのトレードオフを理解しよう

# はじめに
こんにちは。細々とプログラミングをしている[sotanengel](https://x.com/sota_eng_prog)です。
この記事は以下の記事の連載です。

https://qiita.com/sotanengel/private/78103dcfe2fa34f8815e


<details>
<summary><strong>他の連載記事</strong> (<em>詳細</em>)</summary>

- [Day 1：型システムを使ってデータ構造を再現しよう](https://qiita.com/sotanengel/private/588a7f7cc03c36fc1adc)
- [Day 2：型システムを用いて共通の挙動を表現しよう](https://qiita.com/sotanengel/private/2a8fc62b6d9f8e916021)
- [Day 3：OptionとResultに対してはmatchを用いずに変換しよう](https://qiita.com/sotanengel/items/b54990dc82d877b9d70d)
- [Day 4：標準のErrorを使おう](https://qiita.com/sotanengel/private/79f683c3d75d09244567)
- [Day 5：型変換を理解しよう](https://qiita.com/sotanengel/private/8cc28c8b6108e5998e23)
- [Day 6：newtypeパターンを活用しよう](https://qiita.com/sotanengel/private/38814f8b5a88aad9eccc)
- [Day 7：複雑な型にはビルダを使おう](https://qiita.com/sotanengel/private/80bc555e95ed56b72f14)
- [Day 8：明示的なループの代わりにイテレータ変換を使用することを検討しよう](https://qiita.com/sotanengel/private/f7f4fc03f0b2dc445bf2)
- [Day 9：標準トレイトに習熟しよう](https://qiita.com/sotanengel/private/24a8ad443fc23e779ccd)
- [Day 10：RIIパターンにはDropトレイトを実装しよう](https://qiita.com/sotanengel/private/a3d1b04f98ea80b8989d)
- [Day 11：ジェネリクスとトレイトオブジェクトのトレードオフを理解しよう](https://qiita.com/sotanengel/private/8e2e88d5619b434c3ada)
- [Day 12：デフォルト実装を用いて、実装しなければならないトレイトメソッドを最小限にしよう](https://qiita.com/sotanengel/private/16b02d32732727eaf66f)
- [Day 13：Don't panic](https://qiita.com/sotanengel/private/aa833ce99c8e8b8eecf2)
- [Day 14：リフレクションを避けよう](https://qiita.com/sotanengel/private/6108b22f6644d4fde997)
- [Day 15：可視範囲を最小化しよう](https://qiita.com/sotanengel/private/4075925e05de50dfac96)
- [Day 16：ワイルドカードインポートを避けよう](https://qiita.com/sotanengel/private/555314f77c8318bb1c45)
- [Day 17：パブリックインターフェースのドキュメントを書こう](https://qiita.com/sotanengel/private/b4e8a11522b253ebb62e)
- [Day 18：分別を持ってマクロを使おう](https://qiita.com/sotanengel/private/a33b5ab66a82a26b4adc)
- [Day 19：Clippyに耳を傾けよう](https://qiita.com/sotanengel/private/b4c161bb87fc9e7e813e)
- [Day 20：ユニットテスト以上のものを書こう](https://qiita.com/sotanengel/private/58fcafaee5418b2b2b16)

</details>

また本記事は[Effective Rust(David Drysdale (著), 中田 秀基 (翻訳))](https://www.oreilly.com/library/view/effective-rust/9781098151393/)を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください！
<div style="text-align: center;">
  <figure>
    <a href="https://www.amazon.co.jp/Effective-Rust-%E2%80%95Rust%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%94%B9%E5%96%84%E3%81%97%E3%80%81%E3%82%A8%E3%82%B3%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%92%E6%9C%80%E5%A4%A7%E9%99%90%E3%81%AB%E6%B4%BB%E7%94%A8%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE35%E9%A0%85%E7%9B%AE-David-Drysdale/dp/4814400942"  target="_blank">
      <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1758960/cb1245be-02d6-0c74-8b79-e46288f2ea73.png" alt="商品画像" width="50%">
    </a>
  </figure>
</div>

# 今日の内容
## 概要
Rustには似たような機能を提供する

- ジェネリティクス
- トレイトオブジェクト

が存在します。
そこで今回はこれらの手法のトレードオフを学びましょう。

## ジェネリクスでトレイト制約を書いてみよう
### 問([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/exercises/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/1_generics.rs))
`print_area`に`Area`トレイト制約を追加してください。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算するトレイト
trait Area {
    fn area(&self) -> f64;
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// トレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// トレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// TODO: Areaのトレイト制約を追加してください。
fn print_area(shape: &T) {
    println!("面積は: {:.2}", shape.area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    print_area(&rectangle);
    print_area(&circle);
}
```

</details>

### 解答([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/solutions/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/1_generics.rs))
コード参照。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算するトレイト
trait Area {
    fn area(&self) -> f64;
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// トレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// トレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// Areaの面積を計算するジェネリック関数
fn print_area<T: Area>(shape: &T) {
    println!("面積は: {:.2}", shape.area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    print_area(&rectangle);
    print_area(&circle);
}
```

</details>

## トレイトオブジェクトでトレイト制約を書いてみよう
### 問([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/exercises/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/2_trait_objects.rs))
先ほどと同じコードに対して、トレイトオブジェクトで制約を書いてみましょう。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算するトレイト
trait Area {
    fn area(&self) -> f64;
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// トレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// トレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// TODO: Areaのトレイト制約を「トレイトオブジェクト」で追加してください。
fn print_area(shape: ) {
    println!("面積は: {:.2}", shape.area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    print_area(&rectangle);
    print_area(&circle);
}
```

</details>

### 解答([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/solutions/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/2_trait_objects.rs))
コード参照。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算するトレイト
trait Area {
    fn area(&self) -> f64;
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// トレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// トレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// Areaの図形の面積を計算する関数（トレイトオブジェクト版）
fn print_area(shape: &dyn Area) {
    println!("面積は: {:.2}", shape.area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    print_area(&rectangle);
    print_area(&circle);
}
```

</details>

## 結局どっちがいいの？
書籍では以下のようにまとめられていました。

1. ジェネリクスを使うとコードサイズが大きくなる
    1. 全ての型についてコンパイル時にコードがコピーされてしまうため
1. ジェネリクスのトレイトメソッドの呼び出しはトレイトオブジェクトよりも少し速い
    1. 参照解消の経路のため
1. ジェネリクスの方がコンパイル時間が長い傾向がある

## トレイト制約をトレイトに使用しましょう
### 問([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/exercises/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/3_trait_for_trait.rs))
`Area`トレイトを`Shape`にトレイト制約として実装しましょう。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算する基本トレイト
trait Area {
    fn area(&self) -> f64;
}

// TODO: Areaのトレイト制約をShapeに追加してください。
trait Shape {
    fn double_area(&self) -> f64 {
        self.area() * 2.0
    }
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// AreaトレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// ShapeトレイトをRectangleに実装
impl Shape for Rectangle {}

// AreaトレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// ShapeトレイトをCircleに実装
impl Shape for Circle {}

// Shapeを受け取る関数
fn print_double_area<T: Shape>(shape: &T) {
    println!("2倍の面積は: {:.2}", shape.double_area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    print_double_area(&rectangle);
    print_double_area(&circle);
}
```

</details>

### 解答([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/solutions/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/3_trait_for_trait.rs))
コード参照。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算する基本トレイト
trait Area {
    fn area(&self) -> f64;
}

// 面積を拡張するトレイト（Areaを制約として持つ）
trait Shape: Area {
    fn double_area(&self) -> f64 {
        self.area() * 2.0
    }
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// AreaトレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// ShapeトレイトをRectangleに実装
impl Shape for Rectangle {}

// AreaトレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// ShapeトレイトをCircleに実装
impl Shape for Circle {}

// Shapeを受け取る関数
fn print_double_area<T: Shape>(shape: &T) {
    println!("2倍の面積は: {:.2}", shape.double_area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    print_double_area(&rectangle);
    print_double_area(&circle);
}
```

</details>

## トレイトオブジェクトを使って、配列に異なる構造体を格納できるようにしましょう
### 問([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/exercises/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/4_include_different_struct.rs))
`shapes`配列に`Rectangle`や`Circle`を格納して、同一のforループで処理できるようにしましょう。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算する基本トレイト
trait Area {
    fn area(&self) -> f64;
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// AreaトレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// AreaトレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// TODO: トレイトオブジェクトとしてAreaを追加してください。
fn print_area(shape: Area) {
    println!("面積は: {:.2}", shape.area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    // 異なる型の参照をVecに格納
    let shapes: Vec<&dyn Area> = vec![&rectangle, &circle];

    // ループで面積を計算
    for shape in &shapes {
        print_area(*shape);
    }
}
```

</details>

### 解答([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/solutions/12_traits_Understand_the_trade_offs_between_generics_and_trait_objects/4_include_different_struct.rs))
コード参照。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// 面積を計算する基本トレイト
trait Area {
    fn area(&self) -> f64;
}

// 長方形の構造体
struct Rectangle {
    width: f64,
    height: f64,
}

// 円の構造体
struct Circle {
    radius: f64,
}

// AreaトレイトをRectangleに実装
impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

// AreaトレイトをCircleに実装
impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

// 全ての図形の面積を表示する関数
fn print_area(shape: &dyn Area) {
    println!("面積は: {:.2}", shape.area());
}

fn main() {
    let rectangle = Rectangle {
        width: 10.0,
        height: 5.0,
    };

    let circle = Circle { radius: 7.5 };

    // 異なる型の参照をVecに格納
    let shapes: Vec<&dyn Area> = vec![&rectangle, &circle];

    // ループで面積を計算
    for shape in &shapes {
        print_area(*shape);
    }
}
```

</details>

# さいごに

もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。

https://github.com/sotanengel/effective-rustlings-jp/issues