2つの関連する機能を持つ特性があります。
_trait WithConstructor: Sized {
fn new_with_param(param: usize) -> Self;
fn new() -> Self {
Self::new_with_param(0)
}
}
_
2番目のメソッド(new()
)のデフォルトの実装で、型にSized
をバインドする必要があるのはなぜですか?スタックポインタの操作によるものだと思いますが、よくわかりません。
コンパイラがスタックにメモリを割り当てるためにサイズを知る必要がある場合、次の例ではSized
にT
を必要としないのはなぜですか?
_struct SimpleStruct<T> {
field: T,
}
fn main() {
let s = SimpleStruct { field: 0u32 };
}
_
おそらくすでにご存じのとおり、Rustの型はサイズとサイズを変更できます。サイズが指定されていないタイプは、その名前が示すように、このタイプの値を格納するために必要なサイズがコンパイラに知られていません。たとえば、_[u32]
_は_u32
_ sのサイズなし配列です。要素の数はどこにも指定されていないため、コンパイラはそのサイズを認識しません。別の例は、次のようなベアトレイトオブジェクトタイプです。 、Display
、型として直接使用される場合:
_let x: Display = ...;
_
この場合、コンパイラーは実際にどのタイプがここで使用されているかを認識しておらず、消去されているため、これらのタイプの値のサイズを認識していません。上記の行は無効です-サイズを知らずにローカル変数を作成することはできません(スタックに十分なバイトを割り当てるため)、そしてサイズなしの型の値を引数として関数に渡すことも、関数から返すこともできません。
サイズなしの型は、ポインターを介して使用できますが、追加の情報-スライスで使用可能なデータの長さ(_&[u32]
_)または仮想テーブルへのポインター(_Box<SomeTrait>
_)を使用できます。ポインタは常に固定された既知のサイズであるため、ローカル変数に格納して、関数に渡したり、関数から返したりできます。
具象型を考えると、サイズ付きかサイズなしかをいつでも言うことができます。ただし、ジェネリックスを使用すると、問題が発生します-型パラメーターのサイズが設定されているかどうか。
_fn generic_fn<T>(x: T) -> T { ... }
_
T
のサイズが指定されていない場合、サイズの指定されていない値を直接渡すことができないため、このような関数の定義は正しくありません。サイズが決まっている場合は、すべて問題ありません。
Rustでは、すべてのジェネリック型パラメーターはデフォルトですべての場所でサイズが設定されます-関数、構造体、およびトレイトで。これらには暗黙のSized
バインドがあります。Sized
はサイズ指定されたタイプをマークするための特性です:
_fn generic_fn<T: Sized>(x: T) -> T { ... }
_
これは、圧倒的な回数でジェネリックパラメーターのサイズを変更する必要があるためです。ただし、サイズをオプトアウトしたい場合があります。これは_?Sized
_バインドで実行できます。
_fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... }
_
これで_generic_fn
_をgeneric_fn("abcde")
のように呼び出すことができ、T
はサイズ変更されていないstr
でインスタンス化されますが、これは問題ありません。この関数はT
への参照を受け入れるため、問題はありません。
ただし、サイズの問題が重要な別の場所があります。 Rustの特性は、常にいくつかのタイプに実装されています:
_trait A {
fn do_something(&self);
}
struct X;
impl A for X {
fn do_something(&self) {}
}
_
ただし、これは利便性と実用性の目的でのみ必要です。特性を定義して、常に1つの型パラメーターを取り、その特性が実装されている型を指定しないようにすることができます。
_// this is not actual Rust but some Rust-like language
trait A<T> {
fn do_something(t: &T);
}
struct X;
impl A<X> {
fn do_something(t: &X) {}
}
_
これがHaskell型クラスのしくみであり、実際に、トレイルが実際に下位レベルでRust=)に実装されている方法です。
Rustの各トレイトには、Self
と呼ばれる暗黙のタイプパラメータがあり、このトレイトが実装されるタイプを指定します。これは、トレイトの本文で常に使用できます。
_trait A {
fn do_something(t: &Self);
}
_
ここで、サイズの問題が問題になります。 Self
パラメーターのサイズはありますか?
いいえ、RustではSelf
はデフォルトではサイズ設定されていません。各特性には、Self
にバインドされた暗黙の_?Sized
_があります。サイズなしの型のために実装でき、まだ機能する多くの特性があるため、これが必要とされる理由の1つ。たとえば、参照によってSelf
のみを取得して返すメソッドのみを含むすべての特性は、サイズなしの型に対して実装できます。動機についての詳細は RFC 546 で読むことができます。
特性とそのメソッドのシグネチャのみを定義する場合、サイズは問題になりません。これらの定義には実際のコードがないため、コンパイラーは何も想定できません。ただし、暗黙的なSelf
パラメーターを使用するためデフォルトのメソッドを含む、この特性を使用する汎用コードの作成を開始するときは、サイズを考慮する必要があります。 Self
はデフォルトではサイズが設定されていないため、デフォルトの特性メソッドは値によってSelf
を返すことも、値としてパラメータとして受け取ることもできません。したがって、Self
のデフォルトのサイズを指定する必要があります。
_trait A: Sized { ... }
_
または、Self
のサイズが指定されている場合にのみメソッドを呼び出せるように指定できます。
_trait WithConstructor {
fn new_with_param(param: usize) -> Self;
fn new() -> Self
where
Self: Sized,
{
Self::new_with_param(0)
}
}
_
サイズなしの型でこれを行うとどうなるか見てみましょう。
new()
moves呼び出し元へのnew_with_param(_)
メソッドの結果。しかし、型のサイズが指定されていない限り、何バイトを移動する必要がありますか?私たちは単に知ることはできません。そのため、移動のセマンティクスにはSized
型が必要です。
注:さまざまなBox
esは、まさにこの問題に対してランタイムサービスを提供するように設計されています。