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ソース自体を検索すると、Any
と Error
だけがこの特定の機能を使用しているように見えます。私がソースコードを見た一握りの箱でこれが使われているのを見たことがありません。
オブジェクトにすることができる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_impl
のself
引数が借用ポインターであり、独自の有効期間パラメーターがあることに注意してください。 self
の型は、完全な形式では&'b (dyn Foo + 'a)
のようになります(演算子の優先順位のために括弧が必要です)。 Box<u8>
はそのu8
を所有しています–何も借りていません–なので、それから&(dyn Foo + 'static)
を作成できます。一方、&42u8
は&'b (dyn Foo + 'a)
を作成します。'a
はスタック上の非表示変数に配置され、トレイトオブジェクトはこの変数を借用するため、'static
は42u8
ではありません。 (ただし、それは実際には意味がありません。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
(特性オブジェクト)であると結論付けることができます。
私容疑者理由は非常に単純です:オーバーライドされるかどうか?
trait
ブロックに実装されたメソッドは、trait
の実装者によってオーバーライドでき、デフォルトを提供するだけです。
一方、impl
ブロックに実装されたメソッドはオーバーライドできません。
この推論が正しければ、生涯との相互作用に関するFrancisGagnéのより完全な回答を参照してください。y.foo_in_impl()
で発生するエラーは、洗練されていないだけです。機能しているはずです。