Rust学习笔记 (九)泛式,特征和生命周期

#ProgrammingLanguage/Rust

泛式数据类型:

在前几章的内容中,其实就有用到泛式类型。

可以简单的将泛式类型,理解为具体类型的抽象替代品。在rust中可以使用泛式定义的函数,结构体,枚举,和方法(methods)。

泛式函数定义

将多个类似的输入不同类型参数的函数,抽象成一个输入泛式类型参数的函数(类似C++的重用)

在以往的编程经验中,特别在C里,会需要处理不同类型的输入参数,但是函数题本身十分类似,如下代码,会显得程序臃肿且重复,写起来也特别没有积极性。

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest_i32(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest_char(&char_list);
    println!("The largest char is {}", result);
}

在rust里面,可以将上面的两个相似的函数,合并为一个泛式函数。下面的代码,仍会报错,原因稍后讲解。

fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
 --> src/main.rs:5:17
  |
5 |         if item > largest {
  |            ---- ^ ------- T
  |            |
  |            T
  |
  = note: `T` might need a bound for `std::cmp::PartialOrd`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10`.

To learn more, run the command again with --verbose.

语法解释: rust默认使用<>括号,表示泛式类型,取type的缩写,一般会使用T这个名称,不过也可以是其他的名字。

如果要在函数体中使用泛式类型的变量,就需要在函数定义的时候申明参数名称, **function_name ()**, 如果有两种不同泛式类型,可以写作, **function_name <T, U> ()**。

上述代码仍有问题无法编译通过,编译报错提示,目前的代码里,largest函数无法在T所能代表的所有可能的类型上,正常运行。

因为我们要比较函数体中T类型的值,所以只能使用其值可以排序的数据类型。如果启用比较,标准库有 std::cmp::PartialOrd trait,可以使用在数据类型上。这种在泛式数据类型上指定特定的trait的行为,被叫做Trait Bounds(train界限)

结构体中定义泛式类型参数

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let wont_work = Point { x: 5, y: 4.0 };
}

和泛式函数的定义类似,泛式结构体定义 **struct struct_name **

这里需要注意一下,因为只定义了一种泛式类型T,在程序看来,结构中的x和y是一个类型。

在main函数中的调用,就会报错,编译的时候,认为x是i32 ,y是float是两种类型的数据。这个时候,就必须使用多个泛式类型参数了。

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

枚举中定义泛式类型参数

enum Option<T> {
    Some(T),
    None,
}

这个时候再来看Option就会有更深一层的理解。 这是一个泛式类型T的枚举,这个枚举有两个变量:Some(泛式类型T的一个值)和None(没有任何类型的值)。

和结构体类似,枚举也可以使用多个不同类型的泛式数据。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

枚举Result ,有两个泛式数据类型T和E,有两个变量,其中Ok是T的可能的一个具体类型,Err是E可能的一个具体类型。 如果在以下示例代码中:

use std::fs::File;
fn main() {
	let f = File::open("hello.txt");
	let f = match f { 
		Ok(file) => file, 
		Err(error) => {
			panic!("There was a problem opening the file: {:?}", 			error) 
		},
	}; 
}

如果文件打开正确,T实际代表的是数据类型是std::fs::File,如果文件打开报错,E实际代表的数据类型是std::io::Error。

在代码中如果有多个结构或枚举定义,这些数据结构仅在它们所持有的值的类型上有所不同的话,可以通过使用泛型类型来避免重复。

在method中定义泛式类型

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

通过在impl之后将T声明为通用类型,Rust可以识别Point中尖括号中的类型是通用类型,而不是具体类型。

结构定义中的泛型类型参数可以与你在该结构的method定义中使用的参数不相同。

struct Point<T, U> {
    x: T,
    y: U,
}

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,
        }
    }
}

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);
}

上面这个例子的目的,是为了演示这么一种情况,有一些泛式参数在impl申明,还有一些泛式参数在method申明。

Traits 抽象定义一种共同的行为

比方说对于文本显示,Twitter有一个通过method表现的行为display_text, 同样的Email,也有一个类似的行为. 那么我们就可以通过使用trait,用一种抽象的方式来定义一种共通过的行为display_text.

还可以通过trait bounds去指定泛式类型具有特定的行为.

比方说,按上面的例子,我们可以指定一个泛式类型Words,所表示的具体类型,必须有指定的行为display_text.

定义一个Trait

类型的行为包含我们可以在该类型上调用多个方法(方法只是类型行为的一种) 如果我们可以对所有这些类型调用相同的方法,则不同的类型具有相同的行为。

Trait定义了一种方式,可以将method签名(申明)集合在一起,定义为了完成某些目的而所需要的一组行为. 按照上面的例子,display_text就是一个trait,将不同类型的相同行为,抽象到一起.

例程: 们现在定义了多个不同的结构体类型,包含有不同种类和数量的文本. NewsArticle结构包含有一条新闻的文本,Tweet结构包含有一条最长280个字符的推文. 两种结构还各自包含有自己独特的内容.

我们想创建一个媒体聚合库,来显示各类文本结构体的文本摘要内容. 为了做到这点,每一个聚合库支持的文本类型,都必须要支持显示摘要的行为 method summarize.

#![allow(unused_variables)] 
fn main() { 
	pub trait Summary { 
		fn summarize(&self) -> String; 
	} 
} 

代码分析:定义了一个trait Summary,Summary是一组行为的抽象集合,每一个具有Summary trait的类型,都要实现一个叫做summarize的方法,且返回一个String.

Trait定义的是一种行为的抽象集合,并不需要在trait中定义具体的实现,所以在String后使用分号. (这里类似Clss的函数申明)

每一个实现这个trait的类型,都必须实现自身的 fn summarize(&self) -> String;行为. NewsArticle 的 summarize 就是 文章的简要介绍. Tweet 的 summarize 就是 推文的关键内容.

编译器将强制要求每一个具有Summary trait的类型都必须使用 fn summarize(&self) -> String来准确定义类型自己的summarize method。 说白了就是任何一个类型只要是实现了Summary这个trait,那么都必须按照trait定义的 fn summarize(&self) -> String 方法,实现一个自己的类型的版本的该方法.

一个trait的定义中,可以有多个方法申明.

pub trait Summary { 
fn summarize(&self) -> String; 
fn summarize_author_age(&self) -> u32; 
} 

文本聚合库的实现

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)
    	}
	}
}

常规的方法实现:

struct Rectangle { 
	width: u32, 
	height: u32, 
} 
impl Rectangle { 
	fn area(&self) -> u32 { 
		self.width * self.height 
	} 
} 

调用特定行为方法:

fn main() { 
	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类似于实现常规method。不同点在impl之后,是想要实现的trait的名字(Summary),然后使用for关键字,后紧跟着我们想要实现这个trait的特定的类型。最后在大括号里面,指定我们想要在特殊类型上,实现的方法的具体行为。

注意到当前的代码结构,Summary trait和NewsArticl和Tweet都定义在同一个文件lib.rs同一个范围内,文本聚合库. 如果外界想使用我们的文本聚合库,在其自己的外部库范围实现Summary trait的话,首先需要做的就是把我们的文本聚合库里的trait,引入到外部库的范围内.

use aggregator::Summary; 

这样就能实现外部库在记得Summary trait.

对于trait的实现,有一个限制 Trait或者类型 二者其一 必须是在本地crate(本地库).

例如: 我们可以在我们自己的本地文本聚合库Twitter中实现一个标准库的trait Display.因为类型Tweeter是在本地. 我们也可以在Vec上实现Summary trait,因为Summary trait是在本地. 但是如果我们想在Vec上实现Display就是无法实现的. **此限制是程序的一部分,称为连贯性(凝聚),是一种特殊的孤儿规则** 这样就能避免两个不同的库用相同名字的类型实现同一个trait,rust不知道要使用哪种实现.

默认实现

有时候,一个trait上的部分或者全部method,使用默认行为,而不是要求每种类型都有自己的实现方法,是比较有用的. 如果我们的某一个类型上有具体的实现方法,是会覆盖默认行为的.

#![allow(unused_variables)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {}

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)
        }
    }
}

以上代码,NewsArticle采用的是默认实现. 在NewsArticle上调用summarize,会返回字符串”Read more…”

默认实现方法,可以调用trait中别的方法,即使其他方法没有默认实现(只有申明没有具体实现) 定义:

pub trait Summary {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }

调用:

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());
1 new tweet: (Read more from @horse_ebooks...)

调用过程: Tweet调用默认实现summarize, summarize调用同一个trait中的summarize_author.

请注意,无法从相同method的重写实现中调用默认实现。

Traits 作为参数

定义一个函数notify,这个函数的参数item是一个实现了Summar trait的类型,在函数体中,通过参数item调用summarize方法.

pub fn notify(item: &impl Summary) { 
	println!("Breaking news! {}", item.summarize()); 
} 

pub fn notify(item: &impl Summary) 字面上来看,参数是item,这是一个什么参数,impl实现了Summary trait的一个参数. 这个参数,可以是任何实现了Summary trait的类型. notify函数体中,可以调用Summary trait申明的任何method,比如summarize.

Trait bound 语法

pub fn notify(item: &impl Summary) 这种impl语法形式,在简单的案例中,还能够使用,但实际上是复杂案例的语法糖.

较为复杂的情况:

pub fn notify(item1: &impl Summary, item2: &impl Summary) 

如果我们要强制两个参数具有相同的类型,只能用trait bound来表达

pub fn notify<T: Summary>(item1: &T, item2: &T) 

如果一个参数需要同时满足多个trait,使用 + 语法:

pub fn notify(item: &(impl Summary + Display)) 
pub fn notify<T: Summary + Display>(item: &T) 

如果一个参数会有许多个trait,上面的写法会显得不够简洁.

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) { 

fn some_function<T, U>(t: &T, u: &U) -> i32 
where T: Display + Clone, U: Clone + Debug 
{ 

返回值一个实现了trait的类型

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的类型在闭包和迭代器中是非常有用的. 闭包和迭代器,会产生只有编译器才知道的类型或者产生需要很长代码才能明确指定的类型. Impl trait 语法让你可以很简要的指出,一个函数返回的一些实现了Iterator trait的类型,不需要写一个超极长的类型.

局限性: 然而,仅仅是在返回一个类型的时候,可以使用 impl trait 语法. 例子:

pub trait Summary {
    fn summarize(&self) -> String;
}

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)
    }
}

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,
        }
    }
}

上面的例子中 , impl Summary 的返回值,既可能返回Tweet也可能返回NewsArticle,是无法编译成功的.

回到讲泛式类型的章节留下的问题10-5

fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}
$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
 --> src/main.rs:5:17
  |
5 |         if item > largest {
  |            ---- ^ ------- T
  |            |
  |            T
  |
  = note: `T` might need a bound for `std::cmp::PartialOrd`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10`.

To learn more, run the command again with --verbose.

因为操作符 > 是比标准库std::cmp::PartialOrd中定义的默认方法,我们就需要在函数largest中对泛式类型指定这个taait.

fn largest<T: PartialOrd>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

运行还是会报错

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0508]: cannot move out of type `[T]`, a non-copy slice
 --> src/main.rs:2:23
  |
2 |     let mut largest = list[0];
  |                       ^^^^^^^
  |                       |
  |                       cannot move out of here
  |                       move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
  |                       help: consider borrowing here: `&list[0]`

error[E0507]: cannot move out of a shared reference
 --> src/main.rs:4:18
  |
4 |     for &item in list {
  |         -----    ^^^^
  |         ||
  |         |data moved here
  |         |move occurs because `item` has type `T`, which does not implement the `Copy` trait
  |         help: consider removing the `&`: `item`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `chapter10`.

To learn more, run the command again with --verbose.

list参数有可能具有未实现Copy trait的类型 所以 仅使用实现复制特征的那些类型来调用此代码

除了使用 PartialOrd这个用于比较的trait,还需要泛式类型T同时支持Copy这个trait,所以使用 + 语法

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

使用Trait bounds 有条件的实现method

通过使用trait bounds和带泛式类型参数的的impl,我们就可以用有指定trait的类型有条件的实现methods.

#![allow(unused_variables)]
fn main() {
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);
        }
    	}
	}
}

我们可以有条件的,让一个trait的任何类型,实现另一个trait. 例如 标准库 在任何实现了Display trait的类型上又实现了ToString trait

impl<T: Display> ToString for T { 
// --snip-- 
} 

类似于一种迭代的用法.

Because the standard library has this blanket implementation, we can call the to_string method defined by the ToString trait on any type that implements the Display trait. 让我们可以在任何实现了Display trait的类型上,调用ToString trait实现的方法to_string.

换句话说,只要你的类型实现了Display,就可以调用to_string这个method.

#![allow(unused_variables)] 
fn main() { 
	let s = 3.to_string(); 
} 

我们可以在实现了Display trait的整形类型上调用to_string method.

trait和trait bounds使我们可以编写使用泛式类型参数的抽象行为来减少重复的代码,同时还向编译器指定我们希望泛式类型具有特定行为。

Validating Reference with Lifetimes

在rust中,每一个reference都有一个生命周期,就是reference有效的范围。

防止出现reference生命周期过早结束

生命周期的主要目的,就是防止出现混乱引用,类比C的指针所指向的内容非法后,仍然可以调用指针操作。

场景类似于C语言中,函数返回一个函数内的临时指针变量。这时候程序是不稳定的。

{
        let r;                // ---------+-- 'a
                              //          |
        {                     //          |
            let x = 5;        // -+-- 'b  |
            r = &x;           //  |       |
        }                     // -+       |
                              //          |
        println!("r: {}", r); //          |
    }                         // -----

注释描述了变量的生命周期。

上面的代码,外部括号内定义的变量r,生命周期参考注视‘a的范围,内部括号定义了变量x,生命周期参考注释‘b的范围。

x在内部括号结束的时候,就无效了,这时候外部变量r仍然是x的引用,编译就会报错。

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0597]: `x` does not live long enough
  --> src/main.rs:7:17
   |
7  |             r = &x;
   |                 ^^ borrowed value does not live long enough
8  |         }
   |         - `x` dropped here while still borrowed
9  | 
10 |         println!("r: {}", r);
   |                           - borrow later used here

其实就是x变量存活的时间不够长,因为x的reference使用还没有完结。

The Borrow Checker

将之前的代码修改为

 {
        let x = 5;            // ----------+-- 'b
                              //           |
        let r = &x;           // --+-- 'a  |
                              //   |       |
        println!("r: {}", r); //   |       |
                              // --+       |
    }                  

就完全没问题了,其实总结起来,任何reference的生命周期,必须要大于等于调用找个reference的操作范围。可以类比理解为不能使用一个非法指针。生命周期就是来确保这一点的。

Generic Lifetimes in Functions

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

error[E0106]: missing lifetime specifier --> src/main.rs:1:33
|
1 | fn longest(x: &str, y: &str) -> &str {
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `x` or `y`

返回类型需要一个通用生命周期参数,因为Rust无法分辨返回的参考是x还是y.

要解决此错误,我们将添加通用的生命周期参数,这些参数定义了引用之间的关系,以便borrow checker可以进行分析。

生命周期注释语法

生命周期注释不会更改任何引用的有效期限。就像函数定义指定泛式类型参数时函数可以接受任何类型一样,函数可以通过指定通用生命周期参数来接受具有任何生命周期的引用。

生命周期注释语法:生命周期参数用符号 ‘ 开头,通常全部为小写字母且比较简短。将生命周期参数放在引用的&符号后面,并用一个空格隔开引用的类型。

&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

如果只是单独的使用一个生命周期参数,没有任何意义,因为生命周期参数的目的是告诉Rust多个引用的通用生命周期参数如何相互关联。

如果我们定义两个 引用具有相同的 生命周期参数,那么编译器就会认为两个引用有相同的生命周期。

Lifetime Annotations in Function Signatures

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

⚠️: <'a>
⚠️: 函数longest中的生命周期参数表示,作为输入参数的两个引用和作为返回值的引用变量,具有相同的生命周期‘a


请记住,当我们在此函数中指定生命周期参数时,我们不会更改任何传入或返回的值的生命周期。

以上代码表示的意思就是,通用生命周期参数‘a的实际值应该是等于x和y,中运行时生命周期较短的那一个。因为我们申明了返回引用具有相同的生命周期参数‘a,也就是说返引用的生命周期也会等于x和y中生命周期较短的那一个。

结构体定义中使用生命周期注释

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,
    };
}

此注释表示ImportantExcerpt实例不能超过其在部分字段中保存的引用的寿命。

Lifetime Elision

第一条规则是作为引用的每个参数都有自己的生命周期参数. 第二条规则是,如果仅存在一个输入寿命参数,则将寿命分配给所有输出寿命参数. 第三个规则是,如果有多个输入生存期参数,但是其中一个是&self或&mut self,因为这是一种方法,因此将self的生存期分配给所有输出生存期参数。

Lifetime Annotations in Method Definitions

结构字段的生命周期名字,必须要申明在关键字impl之后,然后在结构体名称后调用,因为这些生命周期是结构类型的一部分。

struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

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,
    };
}

静态生命周期

这意味着该带有静态生命周期的引用可以在程序的整个过程中有效。

let s: &'static str = "I have a static lifetime.";

Generic Type Parameters, Trait Bounds, and Lifetimes Together

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest_with_an_announcement(
        string1.as_str(),
        string2,
        "Today is someone's birthday!",
    );
    println!("The longest string is {}", result);
}

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
    }
}

总结