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.";

 

posted @ 2020-10-26 16:48  雪溯  阅读(144)  评论(0)    收藏  举报