Rust Lang Book Ch.10 Generic Types, Traits. and Lifetimes
泛型
在函数中
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.";

浙公网安备 33010602011771号