トレイト境界 structとimpl どちらにつけるべきか

ジェネリクス in Rust

ジェネリック型パラメーターを持つ関数や型をジェネリック(generic)と呼ぶ。

参照: ジェネリクス Generics - あるマのメモ書き

Rustでは、型の宣言側(struct, enum, trait)と実装側(impl)、両方においてジェネリック型パラメーターを宣言することができる。

定義側
struct Either<L, R> (L, R);
実装側
impl<L> Either<L, bool> {
    fn get_left(&self) -> L {
        self.0
    }
}

トレイト境界

ジェネリック型パラメーターに対して、特定のトレイトを実装していることを宣言することが出来る。

トレイト境界 at 定義側

型がそのトレイトに対して完全に依存している場合は、その型に対してトレイト境界を付ける。 そうすることで、impl側でもジェネリクスのときは、トレイト境界を強制する。 また具体型を型引数に指定する場合は、トレイトを実装している必要がある。

use std::fmt::Display;

struct Show<T: Display> {
    x: T,
}

// impl側にもトレイト境界が必要となる
impl<T: Display> Show<T> {
    fn print(&self) {
        println!("{:#}", self.x);
    }
}

// もしくは、型引数にはDisplayトレイトが実装された型のみ取ることが出来る
impl Show<String> {
...
}

多重定義となるため避ける

ただstruct側で境界を付けることは、それほど多くならないと思われる。 またHaskellでは、実装側で型クラス制約をつける

トレイト境界 at 実装側

特定のトレイトに対してのみ、追加の振る舞いを提供したい場合などが実現できる。

trait GoldCustomer {
    fn get_store_points(&self) -> i32;
}

struct Store<T> {
    customer: T,
}

// TがGoldCustomerのときのみのメソッドを追加することができる。
impl <T: GoldCustomer> Store {
    fn choose_reward(customer: T) {
        
    }
}

sturct側で境界を課さないことで幅広い実装が可能となる。

struct Pair<T> {
    a: T,
    b: T,
}

impl<T> Pair<T>
where
    T: std::ops::Add<T, Output = T>,
{
    fn sum(self) -> T {
        self.a + self.b
    }
}

impl<T> Pair<T>
where
    T: std::ops::Mul<T, Output = T>,
{
    fn product(self) -> T {
        self.a * self.b
    }
}

まとめ

トレイト境界

  • 定義側で境界する
    • 特定のトレイトに対して型を依存させる
  • 実装側で境界する
    • 特定のトレイトに対してのみ振る舞いを追加できる

参照

stackoverflow.com