zoukankan      html  css  js  c++  java
  • 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.";
  • 相关阅读:
    Educational Codeforces Round 85 D. Minimum Euler Cycle(模拟/数学/图)
    Educational Codeforces Round 85 C. Circle of Monsters(贪心)
    NOIP 2017 提高组 DAY1 T1小凯的疑惑(二元一次不定方程)
    Educational Codeforces Round 85 B. Middle Class(排序/贪心/水题)
    Educational Codeforces Round 85 A. Level Statistics(水题)
    IOS中的三大事件
    用Quartz 2D画小黄人
    strong、weak、copy、assign 在命名属性时候怎么用
    用代码生成UINavigationController 与UITabBarController相结合的简单QQ框架(部分)
    Attempting to badge the application icon but haven't received permission from the user to badge the application错误解决办法
  • 原文地址:https://www.cnblogs.com/xuesu/p/13879311.html
Copyright © 2011-2022 走看看