読者です 読者をやめる 読者になる 読者になる

Rustトレイトの既定実装と否定実装

概要: SendやSyncなど一部のトレイトで採用されている機能である、既定実装と否定実装の挙動を調べた。

既定実装と否定実装について

既定実装(デフォルト実装, 自動実装, オート実装, default impl, auto impl)と否定実装(negative impl)はRust RFC 0019: Opt-in Builtin Traits (OIBITs)にて、unsafeトレイトとともに規定されている機能である。

OIBITsは、その名前に反して、「言語に組み込みとは限らないマーカートレイトについて、オプトアウト方式での実装を可能にする」という、非常に天邪鬼な機能である。

既定実装と否定実装は組み合わせて使われる。実際の標準ライブラリの例を見るとわかりやすい。

#![feature(optin_builtin_traits)]

// unsafeトレイト
unsafe trait Send {}
// unsafeな既定実装
unsafe impl Send for .. {}
// 否定実装
impl<T:?Sized> !Send for Arc<T> {}

標準ライブラリのOIBITs

1.16.0現在、標準ライブラリで既定実装と否定実装が使われているのは以下の4つである。

既定実装

既定実装は以下のような構文を持つ。

unsafe impl Trait for .. {}
impl Trait for .. {}

ただし、以下の制約がある。

  • この実装は生存期間・型パラメーターをとることができない。
  • 実装対象のトレイトは生存期間・型パラメーターをとることができない。
  • 既定実装は中身を持つことができない。したがってマーカートレイトに対してしか使うことができない。 (このトレイトがwhere節をもつことは可能である。)

既定実装の適用規則

既定実装をもつトレイトは、トレイト束縛の解決時にその内容が参照される。以下の条件で、既定実装が適用される。 (rustc::traits::select 2104行目)

  • 否定実装を含め、他の実装が見つかっていない。 (rustc::traits::select 1122行目)
    • where制約を満たしていない実装でも、型のパターンが一致していれば、この時点では「見つかっている」と見なす。
  • トレイト自身にwhere制約がある場合、それを満たしている。
  • この型を構成する型も、同じトレイトを実装している。ただし、「構成する型」は以下のように定義される。
    • PhantomData<T> は、 T から構成される。
    • それ以外の構造体や列挙体は、メンバ全てから構成される。 (クロージャも似ているが #27086 が関係してくる)
    • 配列もポインタも参照も、その元の型から構成される。

これらが where に追加されたのと同様に振る舞うと考えればよい。

既定実装の上書き

上に書いてあるように、既定実装の生成するwhere制約が所望のものではなかったときは、自分で定義した実装で上書きできる。

例えば、

#![feature(optin_builtin_traits)]

trait Foo {}

impl Foo for .. {}

struct B<X>(X);

の場合、 B<X> には以下の実装が生成されたような扱いになる。

impl<X: Foo> Foo for B<X> {}

例えば、以下のように書くと、 B に対しては既定実装は適用されなくなり、かわりに明記された実装が使われるようになる。 XはFooでなくてもよいが、Barである必要があるようになる。

trait Bar {}
impl<X: Bar> Foo for B<X> {}

以下の場合、 B<u32> については上記の実装を適用し、それ以外については B<X> であっても既定実装を適用することになる。

impl Foo for B<u32> {}

否定実装

否定実装は以下のような構文を持つ。

impl<'a, X> !Trait for Type<'a, X> {}

ただし、以下の制約がある。

  • 当たり前だが、inherent implとして (impl !Type {}のように) 使うことはできない。
  • 実装対象のトレイトは既定実装を持つ必要がある。したがって否定実装は中身を持たないし、マーカートレイトに対してしか使うことができない。 (このトレイトがwhere節をもつことは可能である。)

否定実装は、この実装が採用されたときにエラーとなるという点以外は、普通の実装と同様である。これは既定実装の上書きをするときに、where条件を変えるのではなく実装を丸ごと禁止するのに使う。

まとめ

既定実装と否定実装は Send/Sync/UnwindSafe/RefUnwindSafe のように、基本的には特定の継承ルールに基づいてマーカートレイトを実装させたいが、特定の型に対しては異なるルールを適用したいときに用いる。