Rust: 継承を空トレイトを用いて実現する方法

Rustでは継承が直接サポートされていませんが、トレイト(インターフェースに似ている)を使うことで似たような機能を実現できます。この記事では、まずJavaでクラスベースオブジェクト指向言語の継承を使ってトランザクション処理を実装する例を紹介し、その後、Rustで同じ機能を空のトレイトを使って実現する方法を紹介します。

# JavaでのTransactionクラスの継承の例

JavaでTransactionクラスを実装する場合、抽象基底クラスを利用して処理の再利用や拡張性を確保します。例えば以下のようなコードが考えられます。

```java
abstract class Transaction {
    private double price;
    private int amount;

    public Transaction(double price, int amount) {
        this.price = price;
        this.amount = amount;
    }

    public double total_price() {
        return price * amount;
    }
}

class Sell extends Transaction {
    public Sell(double price, int amount) {
        super(price, amount);
    }

    // Sell-specific methods can be added here
}

class Buy extends Transaction {
    public Buy(double price, int amount) {
        super(price, amount);
    }

    // Buy-specific methods can be added here
}
```

このコードでは、`Transaction`クラスが基底クラスとして売買トランザクションの共通部分を担当し、`Sell`と`Buy`というクラスがそれぞれTransactionクラスを継承しています。

# RustでのTransactionクラスの空トレイトによる実装

では、Rustでも同じ機能を実現するため、以下のように空のトレイトと共通の実装を利用して実装します。

```rust
use std::marker::PhantomData;

pub trait TransactionType {}
pub struct Sell;
pub struct Buy;
impl TransactionType for Sell {}
impl TransactionType for Buy {}

pub struct Transaction<T: TransactionType> {
    price: f64,
    amount: i32,
    _marker: PhantomData<T>,
}

impl<T: TransactionType> Transaction<T> {
    pub fn new(price: f64, amount: i32) -> Self {
        Self {
            price,
            amount,
            _marker: PhantomData,
        }
    }

    pub fn total_price(&self) -> f64 {
        self.price * self.amount as f64
    }
}

fn main() {
    let sell = Transaction::<Sell>::new(10.0, 5);
    let buy = Transaction::<Buy>::new(12.0, 4);

    println!("Sell total: {}", sell.total_price());
    println!("Buy total: {}", buy.total_price());
}
```

ここで、`TransactionType`という空のトレイトは、Javaでの基底クラス`Transaction`に相当します。それに対応した`Sell`や`Buy`がトレイトを実装することで、そのトランザクションタイプに応じた機能が提供されます。`Transaction`構造体およびその実装では、共通のフィールドやメソッドが実装されており、Rustでも継承のような機能が実現されています。

## 各部分の解説

Rustのコードの各部分の詳細な解説は以下のとおりです。

1. まず`TransactionType`という空のトレイトを定義し、`Sell`と`Buy`に対してそれぞれ実装させます。これにより、`Transaction`構造体のジェネリック型パラメータとして、`Sell`と`Buy`を指定できるようになります。

    ```rust
    pub trait TransactionType {}
    pub struct Sell;
    pub struct Buy;
    impl TransactionType for Sell {}
    impl TransactionType for Buy {}
    ```

2. 次に、ジェネリックな`Transaction`構造体を定義し、`TransactionType`トレイトを実装している型をジェネリック型パラメータ`T`として受け取ります。また、フィールドとして`price`、`amount`、および`PhantomData<T>`を持ちます。`PhantomData<T>`は実際の値を持たず、コンパイラに型情報を伝えるためだけに使用されます。

    ```rust
    pub struct Transaction<T: TransactionType> {
        price: f64,
        amount: i32,
        _marker: PhantomData<T>,
    }
    ```

3. `Transaction<T>`構造体の実装ブロックでは、`new`関数と`total_price`関数を定義します。`new`関数は引数として価格(`price`)と数量(`amount`)を受け取り、`Transaction`構造体を初期化します。`total_price`関数は価格と数量を掛けた合計金額を取得します。

    ```rust
    impl<T: TransactionType> Transaction<T> {
        pub fn new(price: f64, amount: i32) -> Self {
            Self {
                price,
                amount,
                _marker: PhantomData,
            }
        }
    
        pub fn total_price(&self) -> f64 {
            self.price * self.amount as f64
        }
    }
    ```

4. 最後に、`main`関数で`Transaction<Sell>`および`Transaction<Buy>`オブジェクトを作成し、それぞれの合計金額を表示します。`Sell`と`Buy`が`TransactionType`トレイトを実装しているため、`Transaction`構造体のジェネリック型パラメータとして指定できます。

    ```rust
    fn main() {
        let sell = Transaction::<Sell>::new(10.0, 5);
        let buy = Transaction::<Buy>::new(12.0, 4);
    
        println!("Sell total: {}", sell.total_price());
        println!("Buy total: {}", buy.total_price());
    }
    ```

このように、Rustの空のトレイトとジェネリックを活用することで、クラスベースオブジェクト指向言語の継承の機能に類似したコードの再利用と拡張性を実現できます。

# オーバーライド

ここで、Rustの継承のような機能を他の例でも紹介し、さらなる保守性や拡張性を提供する方法を検討しましょう。例えば、`Transaction`としての振る舞いを拡張し、特定の税率による税金を計算する機能を追加したいとします。

## 1. Taxトレイトの定義

まず、税金の計算を行う`Tax`トレイトを定義します。これにより、トランザクションの種類に応じて独自の税率を設定できます。

```rust
pub trait Tax {
    fn tax_rate() -> f64;
}
```

## 2. SellとBuy構造体を拡張

次に、`Sell`と`Buy`構造体それぞれに対して、`Tax`トレイトを実装します。この例では、`Sell`の税率を5%、`Buy`の税率を12%としています。

```rust
impl Tax for Sell {
    fn tax_rate() -> f64 {
        0.05
    }
}

impl Tax for Buy {
    fn tax_rate() -> f64 {
        0.12
    }
}
```

## 3. Transaction構造体の実装を拡張

`Transaction`構造体に新しいメソッド`total_price_with_tax`を追加し、これを使用して税金を含む合計金額を計算します。`total_price_with_tax`メソッドは、`T`が`Tax`トレイトを実装していることを前提としています。

```rust
impl<T: TransactionType + Tax> Transaction<T> {
    pub fn total_price_with_tax(&self) -> f64 {
        let tax = self.total_price() * T::tax_rate();
        self.total_price() + tax
    }
}
```

## 4. メイン関数の実行

最後に、メイン関数で税金を含む合計金額を表示するようにコードを変更します。

```rust
fn main() {
    let sell = Transaction::<Sell>::new(10.0, 5);
    let buy = Transaction::<Buy>::new(12.0, 4);

    println!("Sell total without tax: {}", sell.total_price());
    println!("Buy total without tax: {}", buy.total_price());

    println!("Sell total with tax: {}", sell.total_price_with_tax());
    println!("Buy total with tax: {}", buy.total_price_with_tax());
}
```

このように、Rustの空のトレイトとジェネリックを使って、更なる柔軟性や拡張性が提供されるようにコードを構成することができます。これにより、クラスベースのオブジェクト指向言語の継承に似た機能を実現しつつ、コンパイル時の型安全性が保たれる利点が得られます。

# 派生型に異なる型のフィールドを持たせる

次に、`Transaction`に`SellId`と`BuyId`を追加し、`TransactionType`に`type Id`を持たせて、トランザクション種別ごとにユニークなIDを管理できるようにしましょう。

## 1. TransactionTypeトレイトの拡張

`TransactionType`トレイトに関連型`Id`を追加します。これにより、トランザクション種別ごとに異なるID型を指定できます。

```rust
pub trait TransactionType {
    type Id;
}
```

## 2. SellIdとBuyId構造体を定義

`SellId`と`BuyId`という単純な構造体を定義します。これらは、一意のIDを保持するために使用します。

```rust
pub struct SellId(u64);
pub struct BuyId(u64);
```

## 3. SellとBuy構造体を拡張

`Sell`と`Buy`構造体に対して`TransactionType`トレイトを実装し、それぞれの関連型`Id`に`SellId`と`BuyId`を指定します。

```rust
impl TransactionType for Sell {
    type Id = SellId;
}

impl TransactionType for Buy {
    type Id = BuyId;
}
```

## 4. Transaction構造体にIdフィールドを追加

`Transaction`構造体に`id`フィールドを追加し、`T::Id`型を格納します。また、`new`関数で各IDを受け取るようにします。

```rust
pub struct Transaction<T: TransactionType> {
    id: T::Id,
    price: f64,
    amount: i32,
    _marker: PhantomData<T>,
}

impl<T: TransactionType> Transaction<T> {
    pub fn new(id: T::Id, price: f64, amount: i32) -> Self {
        Self {
            id,
            price,
            amount,
            _marker: PhantomData,
        }
    }
    // その他のメソッド…
}
```

## 5. メイン関数の実行

メイン関数で、IDを持つ`Transaction<Sell>`と`Transaction<Buy>`オブジェクトを作成します。

```rust
fn main() {
    let sell_id = SellId(1);
    let buy_id = BuyId(2);

    let sell = Transaction::<Sell>::new(sell_id, 10.0, 5);
    let buy = Transaction::<Buy>::new(buy_id, 12.0, 4);

    // totalPrice, totalPriceWithTax 等の処理...
}
```

このように、ジェネリックで`TransactionType`に関連型を追加することで、`Transaction<Sell>`と`Transaction<Buy>`がそれぞれ別のID型を持つことができ、柔軟性が向上します。

以上になります。Rustの空のトレイト、ジェネリック、関連型を活用することで、クラスベースのオブジェクト指向言語の継承に似た機能を実現できることを説明できたのではないかと思います。