Rust - Basic - 10 - Generaic Data Type & Traits & Lifetime
Generic: Data Type & Traits & Lifetime
Comprehension
Generic 表现方式 1 : Data Type
// 泛型声明 - 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); }
Generic 表现方式 2 :Traits & Trait Bounds
Trait 意为 “特征”,从基础写法来看,它可以给 struct、函数参数、函数返回值 打上特征
对于 struct,特征可以明确该 struct 有特征内声明的 method,类似于 Java 的
interface
impl structA for traitA
对于 generic 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); } } }
Generic 表现方式 3 :Lifetimes
&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 条规则,以在没有显式注解的时候,计算出 lifetimes references
The first rule is that each parameter that is a reference gets its own lifetime parameter.
第一条:每一个引用输入参数有它自己的生命期参数。
The second rule is if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
第二条:如果只有一个输入生命期参数,这个参数会被分配给所有的输出生命期参数。
The third rule is if there are multiple input lifetime parameters, but one of them is
&self
or&mut self
because this is a method, the lifetime ofself
is assigned to all output lifetime parameters.第三条:如果有多个输入生命期参数,且其中一个是
&self
或&mut self
(method),那么self
的生命期参数会被分配给所有的输出生命期参数。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 } }
Origin
…
Performance of Code Using Generics
You might be wondering whether there is a runtime cost when you’re using generic type parameters. The good news is that Rust implements generics in such a way that your code doesn’t run any slower using generic types than it would with concrete types.
Rust accomplishes this by performing monomorphization of the code that is using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.
…
The compiler uses three rules to figure out what lifetimes references have when there aren’t explicit annotations. The first rule applies to input lifetimes, and the second and third rules apply to output lifetimes. If the compiler gets to the end of the three rules and there are still references for which it can’t figure out lifetimes, the compiler will stop with an error. These rules apply to fn
definitions as well as impl
blocks.
The first rule is that each parameter that is a reference gets its own 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.
The second rule is if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32
.
The third rule is if there are multiple input lifetime parameters, but one of them is &self
or &mut self
because this is a method, the lifetime of self
is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary.