泛型
在函数中
fn largest<T>(list: &[T]) -> &T { let mut largest = list[0]; for item in list { if item > largest { largest = item; } } largest }
在struct中
struct Point<T> { x: T, y: T, } fn main() { let integer = Point { x: 5, y: 10 };//注意如果是{x: 5, y: 4.0},那么就不可能编译,因为编译器无法推测T到底是int还是float let float = Point { x: 1.0, y: 4.0 }; }
在enum中:
enum Result<T, E> { Ok(T), Err(E), }
在method中:
struct Point<T> { x: T, y: T, } impl<T> Point<T> {//注意要在impl之后就声明一次T fn x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x()); }
能够仅仅对泛型中的其中一种具现化提供实现
impl Point<f32> { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } }
struct和函数可以使用不同的泛型列表,例如struct本身使用T, U,方法可以使用W, V
impl<T, U> Point<T, U> { fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } } }
在Rust中,使用泛型的代价是几乎没有的。因为Rust会在编译的时候对泛型做单态化(Monomorphization),为每个泛型所对应的具体实际类型生成对应的代码。例如,对应Some(5)和Some(5.0),编译器会识别到i32和f64都是Option<T>对应的具体类型,因此会生成Option_i32和Option_f64两个enum并完善对应逻辑。
Trait-特性
类似于其他语言的接口。一个trait内可以声明多个函数签名,这些函数在实现了之后就可以像正常成员函数一样调用
pub trait Summary { fn summarize(&self) -> String;//注意这里仅仅提供函数签名。同时要注意这里的成员变量也是要加&self的
//如果这里提供了具体逻辑,就会成为默认实现。
然后再为每种type实现这些trait。与普通的实现不同,这里要在impl之后写对应trait的名字+for。注意,要为了某个类型实现trait具体逻辑,需要这个trait或者这个类型有一方是当前crate中的。例如,可以为Vec<T>实现Summary trait,但是不能为Vec<T>实现Display trait,这一限制被成为orphan rule,是内聚性的一种体现。
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) } } pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } }
在trait内部直接提供函数具体逻辑,就会成为默认逻辑。为具体类型实现trait的时候就不需要为具体类型提供逻辑,可以直接放一个空的impl scope,例如 impl Summary for NewsArticle {}
pub trait Summary { fn summarize(&self) -> String {//默认实现 String::from("(Read more...)") } }
trait的函数能够调用trait的其他函数,因此,合理搭配默认逻辑和需要override的逻辑能够起到事倍功半的效果。
pub trait Summary { fn summarize_author(&self) -> String; fn summarize(&self) -> String { format!("(Read more from {}...)", self.summarize_author()) } }
impl Summary for Tweet { fn summarize_author(&self) -> String { format!("@{}", self.username) } }
let tweet = Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, }; println!("1 new tweet: {}", tweet.summarize());
Trait作为函数参数
trait可以作为函数参数,但是需要搭配impl关键字或者Trait Bound Syntax一同使用。
impl关键字
pub fn notify(item: &impl Summary) {//只有实现了Summary的类型才能作为参数接受 println!("Breaking news! {}", item.summarize()); }
Trait Bound Syntax:搭配泛型使用
pub fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize()); }
用+可以要求参数同时满足多个Traits
pub fn notify(item: &(impl Summary + Display)) {
pub fn notify<T: Summary + Display>(item: &T) {
Trait Bounds加上+加上where语句也可以完成这一任务,而且更加清晰:
fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug {
Trait作为返回值
可以使用impl Trait语法来返回实现了具体trait的类型示例。但是,return impl Trait要求你的函数只可能返回一种类型,比如NewArticle和Tweet都实现了Summary trait,想要根据情况返回NewArticle或者Tweet就是不行的。
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, } }
如下所示,return impl TraitName只能返回单一的类型,否则就会报错: expected Struct XXX, found struct YYY
fn returns_summarizable(switch: bool) -> impl Summary { if switch { NewsArticle { headline: String::from( "Penguins win the Stanley Cup Championship!", ), location: String::from("Pittsburgh, PA, USA"), author: String::from("Iceburgh"), content: String::from( "The Pittsburgh Penguins once again are the best hockey team in the NHL.", ), } } else { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, } | |_|_________^ expected struct `NewsArticle`, found struct `Tweet` } | |_____- `if` and `else` have incompatible types }
Trait作为其他Trait的条件之一
实现一个满足了某个trait bounds对应的类型上面的新trait称为blanket implementation
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);
}
}
}
Lifetimes
引用的生命周期一般是通过推断得到的,但是也可以有额外的注解来自定义生命周期。
Rust使用Borrow Checker来检查变量的生命周期,并且要求引用的生命周期一定要被覆盖在对应变量的生命周期之内,否则就报错。
{ let r; // ---------+-- 'a // | { // | let x = 5; // -+-- 'b | r = &x; // | | } // -+ | // | println!("r: {}", r); // | } // ---------+
当遇到Borrow Checker无法确定生命周期的情况时,即引用进入一个函数再从一个函数返回时,编译器会直接报错。
fn longest(x: &str, y: &str) -> &str { ^ expected lifetime parameter //报错的原因是Borrow Checker不知道返回的究竟是x还是y,因而无法追踪引用的生命周期 if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); }
此时需要告诉编译器引用的生命周期,加'a即为生命周期注解,注解本身没有什么意义,可以认为是在限制多个引用的生命周期的关系不会相互影响。
&i32 // a reference &'a i32 // a reference with an explicit lifetime &'a mut i32 // a mutable reference with an explicit lifetime
Borrow Checker将拒绝不满足annotation的引用作为参数。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { //这里表示参数x,y和返回值的生命周期是必须一致的
//在实际上,这表示返回的生命周期至少要和x,y中生命周期最短的一个一样长
//x, y生命周期最短的那个必须覆盖返回值的生命周期
if x.len() > y.len() { x } else { y } }
fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); ^^^^^^^ borrowed value does not live long enough } println!("The longest string is {}", result); }
如果返回值仅仅与其中一个参数有关,那就只需要声明这个参数与返回值生命周期的关系。
fn longest<'a>(x: &'a str, y: &str) -> &'a str { x }
如果声明和实际关系不符,编译器会报错:
1 | fn longest<'a>(x: &'a str, y: &str) -> &'a str { | ---- help: add explicit lifetime `'a` to the type of `y`: `&'a str` 2 | y | ^ lifetime `'a` required
Struct和Lifetime Annotation
对于成员变量是引用的情况,也可以添加lifetime annotation,但是需要注意所有的引用成员变量都要注明同样的生命周期。
struct ImportantExcerpt<'a> { part: &'a str, } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence, }; }
如何将多个域作为mut ref传出去?
struct Bar{ x: Vec<f32>, y: Vec<f32>, r: Vec<f32>, } struct BarMut<'a>{ x: &'a mut Vec<f32>, y: &'a mut Vec<f32>, r: &'a mut Vec<f32>, } impl Bar{ fn get_mut_parts(& mut self) -> BarMut{ BarMut{ x: &mut self.x, y: &mut self.y, r: &mut self.r, } } } fn test(bar: &mut Bar) -> BarMut{ bar.get_mut_parts() } fn main(){ let mut bar0 = Bar{ x: vec![1.0], y: vec![1.0], r: vec![1.0], }; println!("{:?}", test(&mut bar0).x); }
Lifetime Elision
有的时候无需用'a注明生命周期,这主要是Rust编译器对某些应用场景会自动标上生命周期,这被成为lifetime elision rules。当然,Rust编译器如果猜不出引用的生命周期就会报错了,而这时候就需要程序员来标注。
函数或者成员函数的参数被称为input lifetimes,返回值则被称为output lifetimes。
首先,编译器为每个参数分配一个lifetime parameters,比如'a, 'b, 'c,以此类推,例如fn foo<'a>(x: &'a i32)和fn foo<'a, 'b>(x: &'a i32, y: &'b i32)。接着,如果只有一个input lifetime parameter,那么所有返回值中的引用的生命周期都与这个参数相同。例如fn foo<'a>(x: &'a i32) -> &'a i32。最后,如果存在多个input lifetime parameters,但是其中一个是&self,那么编译器自动设定所有output lifetime parameters与&self或者&mut self的生命周期相同。
所以,对于fn first_word(s: &str) -> &str {这样一个函数,编译器能够自动设定生命周期的关联。
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention please: {}", announcement); self.part//因为一个input lifetime parameter是self,所以这里设置返回值的生命周期和&self绑定。 } }
The Static Lifetime
这里静态生命周期指的是在整个程序运行期间,都必须要有效。
let s: &'static str = "I have a static lifetime.";