ジェネリック: データ型・trait・ライフタイム
理解
ジェネリックの表現 1: データ型
// 泛型声明 - struct struct Point<T> { x: T, y: T, } let point = Point { x: 5, y: 4 }; // 必须是同一类型 struct PointDiffType<T, U> { x: T, y: U, } let point_diff_type = PointDiffType{ x: 5, y: 4.0 }; // 不同类型 // 泛型声明 - impl impl<T> Point<T> { fn x(&self) -> &T { &self.x } } // 泛型声明 - enum enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), } // 一种 mix-up 实现 struct Point<T, U> { x: T, y: U, } impl<T, U> Point<T, U> { // 由于泛型的高灵活性,这里做了巧妙的 field 交叉混合,组合出第三个新泛型实例 // 这种语法可用于 middleware fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } } } fn main() { let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: "Hello", y: 'c' }; let p3 = p1.mixup(p2); println!("p3.x = {}, p3.y = {}", p3.x, p3.y); }ジェネリックを使っても実行時性には影響しない
コンパイル段階で Monomorphization process がジェネリックを具体的な型へ変換するためです。
// 比如使用了 Some 泛型代码 let integer = Some(5); let float = Some(5.0); // 编译期会 Monomorphization 为以下代码 enum Option_i32 { Some(i32), None, } enum Option_f64 { Some(f64), None, } fn main() { let integer = Option_i32::Some(5); let float = Option_f64::Some(5.0); }ジェネリックの表現 2: Traits と Trait Bounds
Trait は「特性」を意味します。基本的な使い方では、struct、関数引数、関数の戻り値に特性を付けられます。
struct では、その特性で宣言した method を持つことを明示できます。Java の
interfaceに似ています。impl structA for traitAgeneric type では、対応する concrete type がこれらの特性(trait bound とも呼ばれます)を持つ場合だけ、対応する関数群を持てると示せます。
impl<T: traitA + traitB + ...> structA<T> {...}関数引数では、対応する特性を持つ引数だけを受け取れるよう制限できます(多態性)。
pub fn notify(item: &impl Summary) {...}関数の戻り値では、戻り値が必ずその特性を持つことを明示できます。
// Defining a Trait pub trait Summary { fn summarize(&self) -> String; } // Implementing a Trait on a Type pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } } // Default Implementations pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } } // Default Implementations 可以调用其它 impl pub trait Summary { fn summarize_author(&self) -> String; fn summarize(&self) -> String { format!("(Read more from {}...)", self.summarize_author()) } } // Traits as Parameters pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); } // 可以使用 generic type 来指定 trait bounds pub fn notify<T: Summary>(item1: &T, item2: &T) {} // 多个 trait 的情况 1 - 使用 + 操作符 pub fn notify(item: &(impl Summary + Display)) { pub fn notify<T: Summary + Display>(item: &T) { // 多个 trait 的情况 2 - 使用 where fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug // Returning Types that Implement Traits fn returns_summarizable() -> impl Summary { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, } } // 可以通过 trait bounds 特性,以实现:有对应 trait 的 generic type,才能有对应的 method use std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } }ジェネリックの表現 3: ライフタイム
&i32 // a reference &'a i32 // a reference with an explicit lifetime &'a mut i32 // a mutable reference with an explicit lifetime // 一般用在 function 定义时 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } // 可用于 struct struct ImportantExcerpt<'a> { part: &'a str, } // Lifetime Elision - 活用 3 条规则 fn first_word(s: &str) -> &str {...} // static 生命期,可以在整个程序运行时保持可用 let s: &'static str = "I have a static lifetime.";コンパイラーは、明示的な注釈がない場合、3 つのルールで reference の lifetime を計算します。
1 のルールは、reference である各パラメーターが固有の lifetime parameter を持つことです。
1 条: reference の各入力パラメーターには独の lifetime parameter があります。
2 のルールは、入力 lifetime parameter が 1 つだけなら、その lifetime をすべての出力 lifetime parameter に割りてることです。
2 条: 入力 lifetime parameter が 1 つだけなら、それをすべての出力 lifetime parameter に割りてます。
3 のルールは、入力 lifetime parameter が複数あり、そのうち 1 つが method の
&selfまたは&mut selfなら、selfの lifetime をすべての出力 lifetime parameter に割りてることです。3 条: 入力 lifetime parameter が複数あり、その 1 つが
&selfまたは&mut self(method)なら、selfの lifetime parameter をすべての出力 lifetime parameter に割りてます。3 つの表現を組み合わせて使う
use std::fmt::Display; fn longest_with_an_announcement<'a, T>( x: &'a str, y: &'a str, ann: T, ) -> &'a str where T: Display, { println!("Announcement! {}", ann); if x.len() > y.len() { x } else { y } }
参照元
…
ジェネリックを使うコードのパフォーマンス
generic type parameter を使うと実行時コストが発生するのでは、と疑問に思うかもしれません。Rust のジェネリック実装では、generic type を使っても concrete type の場合よりコードが遅くなることはありません。
Rust は、ジェネリックを使うコードをコンパイル時に monomorphization することでこれを実現します。Monomorphization とは、コンパイル時に使われる concrete type を埋め、generic code を具体的なコードへ変換する処理です。
…
コンパイラーは、明示的な注釈がない場合、3 つのルールで reference の lifetime を判断します。 1 のルールは入力 lifetime、 2 と 3 のルールは出力 lifetime に適されます。3 つのルールを適しても lifetime を判断できない reference が残ると、コンパイラーはエラーで停止します。これらのルールは fn 定義と impl ブロックの両方に適されます。
1 のルールは、reference である各パラメーターが固有の lifetime parameter を持つことです。 In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on.
2 のルールは、入力 lifetime parameter が 1 つだけなら、その lifetime をすべての出力 lifetime parameter に割りてることです。: fn foo<'a>(x: &'a i32) -> &'a i32.
3 のルールは、入力 lifetime parameter が複数あり、そのうち 1 つが method の &self または &mut self なら、self の lifetime をすべての出力 lifetime parameter に割りてることです。This third rule makes methods much nicer to read and write because fewer symbols are necessary.