• 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.";
  • 相关阅读:
    flash跨域策略文件crossdomain.xml配置详解
    ActionScript 3.0著名开源库 大集合
    SwiftSuspenders 1.6深入浅出1
    关于box2d相关学习教程记录一下
    as3框架集合
    写得蛮好的linux学习笔记七开机流程(收藏)
    写得蛮好的linux学习笔记一目录架构(收藏)
    写得蛮好的linux学习笔记三压缩命令(收藏)
    ListBox自绘,列表显示一系列图片
    Get WEB Page Content over Linux C
  • 原文地址:https://www.cnblogs.com/xuesu/p/13879311.html
Copyright © 2020-2023  润新知