web-dev-qa-db-ja.com

トレイトの一部としてではなく、トレイトにメソッドを実装するのはなぜですか?

Any特性をよりよく理解しようとしているときに、それが 特性自体にimplブロックがある であることがわかりました。この構成の目的がわかりません。特定の名前が付いていてもわかりません。

「通常の」トレイトメソッドとimplブロックで定義されたメソッドの両方で少し実験を行いました。

_trait Foo {
    fn foo_in_trait(&self) {
        println!("in foo")
    }
}

impl dyn Foo {
    fn foo_in_impl(&self) {
        println!("in impl")
    }
}

impl Foo for u8 {}

fn main() {
    let x = Box::new(42u8) as Box<dyn Foo>;
    x.foo_in_trait();
    x.foo_in_impl();

    let y = &42u8 as &dyn Foo;
    y.foo_in_trait();
    y.foo_in_impl(); // May cause an error, see below
}
_

編集者注

RustからRust 1.15.0まで)のバージョンでは、行y.foo_in_impl()によってエラーが発生します。

_error: borrowed value does not live long enough
  --> src/main.rs:20:14
   |
20 |     let y = &42u8 as &Foo;
   |              ^^^^ does not live long enough
...
23 | }
   | - temporary value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...
_

このエラーは後続のバージョンでは存在しなくなりましたが、回答で説明されている概念は引き続き有効です。

この限られた実験から、implブロックで定義されたメソッドはtraitブロックで定義されたメソッドよりも制限が厳しいようです。この方法でロックを解除することには何か特別なことがある可能性がありますが、それが何であるかはまだわかりません。 ^ _ ^

The Rust Programming Languageon traits and trait objects のセクションはありませんRustソース自体を検索すると、AnyError だけがこの特定の機能を使用しているように見えます。私がソースコードを見た一握りの箱でこれが使われているのを見たことがありません。

35
Shepmaster

オブジェクトにすることができるFooという名前のトレイトを定義すると、Rustはdyn Fooという名前のトレイトオブジェクトタイプも定義します。古いバージョンのRustでは、このタイプは呼び出されるだけでしたFooタイプの「dyn」はどういう意味ですか? を参照)。これらの古いバージョンとの下位互換性のために、Fooは引き続きトレイトオブジェクトタイプに名前を付けます。ただし、新しいコードにはdyn構文を使用する必要があります。

トレイトオブジェクトには、実装者のライフタイムパラメータの最短を指定するライフタイムパラメータがあります。その存続期間を指定するには、タイプをdyn Foo + 'aと記述します。

impl dyn Foo {(または古い構文を使用してimpl Foo {)を作成する場合、そのライフタイムパラメーターは指定せず、デフォルトで'staticになります。 y.foo_in_impl();ステートメントに関するコンパイラからのこのメモは、次のことを示唆しています。

注:借用した値は、静的な存続期間中有効である必要があります。

これをより寛容にするために私たちがしなければならないのは、任意の生涯にわたって一般的なimplを書くことです。

impl<'a> dyn Foo + 'a {
    fn foo_in_impl(&self) { println!("in impl") }
}

ここで、foo_in_implself引数が借用ポインターであり、独自の有効期間パラメーターがあることに注意してください。 selfの型は、完全な形式では&'b (dyn Foo + 'a)のようになります(演算子の優先順位のために括弧が必要です)。 Box<u8>はそのu8を所有しています–何も借りていません–なので、それから&(dyn Foo + 'static)を作成できます。一方、&42u8&'b (dyn Foo + 'a)を作成します。'aはスタック上の非表示変数に配置され、トレイトオブジェクトはこの変数を借用するため、'static42u8ではありません。 (ただし、それは実際には意味がありません。u8は何も借用しないため、そのFoo実装は常にdyn Foo + 'staticと互換性がある必要があります...42u8がスタックから借用されるという事実は、'bに影響するのではなく、 'a。)

もう1つの注意点は、トレイトメソッドは、デフォルトの実装があり、オーバーライドされていない場合でもポリモーフィックですが、トレイトオブジェクトの固有のメソッドはモノモーフィックです(トレイトの背後にあるものに関係なく、関数は1つだけです)。例えば:

use std::any::TypeId;

trait Foo {
    fn foo_in_trait(&self)
    where
        Self: 'static,
    {
        println!("{:?}", TypeId::of::<Self>());
    }
}

impl dyn Foo {
    fn foo_in_impl(&self) {
        println!("{:?}", TypeId::of::<Self>());
    }
}

impl Foo for u8 {}
impl Foo for u16 {}

fn main() {
    let x = Box::new(42u8) as Box<dyn Foo>;
    x.foo_in_trait();
    x.foo_in_impl();

    let x = Box::new(42u16) as Box<Foo>;
    x.foo_in_trait();
    x.foo_in_impl();
}

サンプル出力:

TypeId { t: 10115067289853930363 }
TypeId { t: 1357119791063736673 }
TypeId { t: 14525050876321463235 }
TypeId { t: 1357119791063736673 }

特性メソッドでは、基になるタイプ(ここではu8またはu16)のタイプIDを取得するため、&selfのタイプは実装者ごとに異なると結論付けることができます(&u8実装者の場合はu8になり、 &u16実装者の場合はu16–特性オブジェクトではありません)。ただし、固有のメソッドでは、タイプIDがdyn Foo+ 'static)になるため、&selfのタイプは常に&dyn Foo(特性オブジェクト)であると結論付けることができます。

24
Francis Gagné

容疑者理由は非常に単純です:オーバーライドされるかどうか?

traitブロックに実装されたメソッドは、traitの実装者によってオーバーライドでき、デフォルトを提供するだけです。

一方、implブロックに実装されたメソッドはオーバーライドできません。

この推論が正しければ、y.foo_in_impl()で発生するエラーは、洗練されていないだけです。機能しているはずです。生涯との相互作用に関するFrancisGagnéのより完全な回答を参照してください。

5
Matthieu M.