ジェネリックの定義#
私は常にジェネリックを、型システム内のパラメータとして理解してきました。なぜなら、TS では、型と実行コードが分離しているからです。ジェネリックの定義は、関数内の型のようなものであり、引数を渡すことで具体的な型を得ることができます。同時に、ジェネリックを使用して引数、戻り値、または構造体のフィールド間の関係を制約することもできます。
fn get_value<T>(v: T) -> T {
v
}
enum Result<T, U> {
Ok(T),
Err(U)
}
struct List<T> {
items: Vec<T>,
current: T
}
impl<T> List<T> {
fn first(&self) -> T {
&self.items[0]
}
}
構造体にメソッドを定義する際には、特定の型でオーバーロードのような操作を制限することもできます。
impl List<i32> {
fn sum(&self) -> i32 {
let mut result = 0;
for item in &self.items {
result += item
}
result
}
}
// トレイトを指定する
impl<T: Display> List<T> {
fn sum(&self) -> i32 {}
}
Rust のジェネリックコードはパフォーマンスに影響を与えません。コンパイル時にはモノマファイ化が行われ、ジェネリックを使用するコードが具体的な型のコードにコンパイルされます。これは、Vue3 の静的最適化に少し似ています。
トレイト#
これは少しややこしいですね
トレイトは、TS のinterface
やabstract class
に似たもので、ジェネリックに制約や制限を加え、抽象的なジェネリックを具象化し、型に振る舞いを追加するために使用されるものと理解しています。
trait Desc {
fn read(&self) -> &str;
fn default() -> &str {
"default impl"
}
}
impl<T> Desc for List<T> {
fn read(&self) -> &str {
"a list"
}
}
トレイトの実装は関連性の制約を受けます。実装側または実装される側の少なくとも 1 つがローカルスコープ内にある必要があります。このルールにより、他の人がクレートの外部でコードを破壊することを防ぐことができます。
抽象の上に抽象の実装 blank implementations.
// Displayを実装したすべての型にToStringを実装する
impl<T: Display> ToString for T {}
定義した後、制約を追加することができます。
fn get_desc(target: &impl Desc) {}
// トレイトバウンド
fn print_desc<T: Desc + Display>(target: &T) -> impl Display {}
fn print_default_desc<T>(target: &T) where T: Desc + Display {}
ライフタイム#
ライフタイムの存在は、参照の有効性を保証し、ダングリング参照を回避するためです。所有権の制約により、参照されるオブジェクトは参照者よりも短い時間存在することはできません。関数内で参照を作成して返すことはできません。そうでない場合、スコープを抜けるときに元のデータが破棄され、返された参照はダングリング参照になります。したがって、関数が参照を返す場合、その参照は引数の参照と関連している必要があります。この関連は、コードから推論することができない場合があります。たとえば、2 つの参照を渡して 1 つの参照を返す場合、ライフタイムを明示的に指定してコンパイラに推論を補助させる必要があります。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// 論理的にはyにライフタイムを指定する必要はありません
fn first<'a>(x: &'a str, y: &str) -> &'a str {
x
}
struct Person<'a> {
name: &'a str
}
// 静的ライフタイム、いくつかのもののように、プログラム全体で存在することを保証するために使用する必要があります
let s:&'static str = "hello"
省略規則#
Rust には、繰り返しを減らすための予測可能なパターンが組み込まれており、これらの規則の後でも推論できないライフタイムが存在する場合は、明示的に指定する必要があります。以下はその規則です。
- 各参照のライフタイムは、少なくとも 1 つの入力参照のライフタイムと同じでなければなりません。
- 入力参照が 1 つだけの場合、出力参照のライフタイムは入力参照のライフタイムと同じでなければなりません。
- 複数の入力参照があり、そのうちの 1 つが
&self
または&mut self
である場合、出力参照のライフタイムはその入力参照のライフタイムと同じでなければなりません。 - 複数の入力参照があり、そのうちの 1 つが
&self
である場合、出力参照のライフタイムはもう 1 つの入力参照のライフタイムと同じでなければなりません。