RustのTraitは内部に型を持つことがある。関連型(associated type)だ
trait Hoge { type Value; } impl Hoge for Piyo { type Value = Fuga; }
このValue
型についてトレイト境界を付けたい。
つまり、ある型T
があり、型<T as Hoge>::Value
がOtherTrait
を満たす場合のみ使える関数を使いたい。
以下のように書けたらよいのだが。
fn foo<T: Hoge<Value: OtherTrait>>() -> () { // ... }
残念ながらこれは通らない。
error: expected one of `!`, `(`, `+`, `,`, `::`, `<`, or `>`, found `:` --> src/main.rs:11:21 | 11 | fn foo<T: Hoge<Value: OtherTrait>>() -> () { | ^ expected one of 7 possible tokens here
ではどうやって書くかというと、実は割と素直に書くと通る。
fn foo<T>(x: T) -> () where T: Hoge, // ここではまだ何も言わない <T as Hoge>::Value: OtherTrait // これを足す { println!("hello! {}", x.value().other()); }
トレイト境界の左辺にそれ書けるんかい! と思った。
動くサンプルを置いておく。 play.rust-lang.org
さて、似たような、だが少し違うケースがある。今日実際に困っていたのはこちらだ。
まず、自分のクレートに既にcrate::error::Error
を定義してあり、よくある型についてFrom for Error
を実装しているとしよう。
pub struct Error {/* ... */}; type Result<T> = std::result::Result<T, Error>; impl From<std::num::ParseIntError> for Error { fn from(error :std::num::ParseIntError) -> Error { // ... } }
で、何らかのフィールドを持つジェネリクス構造体があるとする。それを読み込めるようにしたい。
pub struct Hoge<T> {/* ... */}; impl<T> std::str::FromStr for Hoge<T> { type Err = Error; fn from_str(line: &str) -> Result<Self> { // ... } }
このfrom_str
でHoge<T>
のフィールド(T
型)を読むためにparse
を使いたい。そのためには、まずT
がFromStr
を実装している必要がある。
impl<T> std::str::FromStr for Hoge<T> where T: std::str::FromStr { type Err = Error; fn from_str(line: &str) -> Result<Self> { // ... } }
それだけではない。rustc
はすぐに、<T as FromStr>::Err
がcrate::error::Error
に変換可能でないとこの関数はコンパイルできなくなることを見抜く。なので、ここまで話してきたような、関連型に対するトレイト境界が必要になるのだ。
ところで、今回はFrom</*...*/> for Error
を実装していたのだった。とすると、これはError
に対して実装されているので、先程までと同じような書き方はできない。
Error
がトレイト境界From<T as FromStr>::Err
を満たしている必要があるからだ。
これも、そのまんま書くと通る。
impl<T> std::str::FromStr for Hoge<T> where T: std::str::FromStr, Error: From<<T as std::str::FromStr>::Err> // これ { type Err = Error; fn from_str(line: &str) -> Result<Self> { // ... } }
それ左辺に書けるの!?(驚愕)
Error
は普通に定義されたただのstruct
だったので、それに対してトレイト境界を設けられるとは思っていなかった。トレイト境界はジェネリクスの型引数に対して課すものだと思っていたからだ。
まあでも、わかってしまえば結構そのまんまだった。試してみるものだ。
追記(1/22)
当初std::ops::Add
の実装をよくわかっておらず、ジェネリクストレイトの型パラメータと関連型を取り違えていた(該当箇所は混乱を避けるため消した)。
std::ops::Add
の実装は以下のようになっている。
pub trait Add<RHS = Self /* 型パラメータ */ > { type Output; // 関連型 fn add(self, rhs: RHS) -> Self::Output; }
RHS
はあくまで型パラメータであり、Output
が関連型だ。RHS
を書くべき場所に関連型を書けないのは当たり前だし、型パラメータにさらなる型パラメータとそのトレイト境界を書けないのも当然だ。