美文网首页
Rust编程语言-10-泛型,Traits,生命周期

Rust编程语言-10-泛型,Traits,生命周期

作者: onemoremile | 来源:发表于2022-01-12 20:28 被阅读0次

    泛型 Generic Type

    如下两个function,实现从slice 切片中找到最大的值并返回,分别为i32类型,char类型分别定义了两个function

    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
    }
    

    代码重复太高了,改成Rust支持的泛型格式

    fn largest<T>(list: &[T]) -> T {
        let mut largest = list[0];
    
        for &item in list {
            if item > largest {
                largest = item;
            }
        }
    
        largest
    }
    

    此处的T可以是i32等类型,但是要注意在silce的iteration中,有个比较大小的符号“>”,该运算符是否在T类中中被支持

    如果不支持,则会看到如下报错

    $ 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
      |
    help: consider restricting type parameter `T`
      |
    1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
      |             ^^^^^^^^^^^^^^^^^^^^^^
    
    For more information about this error, try `rustc --explain E0369`.
    error: could not compile `chapter10` due to previous error
    
    

    需要改成使T满足约束PartialOrd,也就是可以部分排序

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

    Traits 特性

    Traits,英文翻译为“特性”,从功能上相当于Java的interface

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

    举栗子:

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

    对于Tweet和NewsArticle来说方法 summarize是有不同的实现的

    下面notify方法的参数,支持实现了Summary Trait的具体的类型

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

    Trait Bound: T: Summary 约束

    pub fn notify<T: Summary>(item: &T) {
        println!("Breaking news! {}", item.summarize());
    }
    
    pub fn notify(item1: &impl Summary, item2: &impl Summary) {} //item1和item2可能是不同的类型
    
    pub fn notify<T: Summary>(item1: &T, item2: &T) {} //同一种类型
    

    Mutiple Trait Bound:多重约束

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

    在约束中使用where

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

    函数返回值可以使用Trait吗?看如下例子

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

    Rust编译失败,不允许返回 impl Summary;
    解决办法是使用Box<dyn Summary>

    fn largest<T: PartialOrd>(list: &[T]) -> T {} //编译失败!
    
    fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
        let mut largest = list[0];
    
        for &item in list {
            if item > largest {
                largest = item;
            }
        }
    
        largest
    }
    

    改进largest方法,返回&T

    使用Trait Bound有条件的实现方法:

    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);
            }
        }
    }
    此处的cmp_display方法只对支持Display和PartialOrd的类型有效
    

    对所有支持Display Trait的类型实现ToString Trait,从而可以调用to_string()方法

    impl<T: Display> ToString for T {
        // --snip--
    }
    
    let s = 3.to_string(); //调用正常
    
    

    lifetime生命周期

    Rust中的引用都有生命周期,代表引用有效的范围,大部分时候,引用是隐式的,可被推导出来;如果类型有可能被推导为多种可能性,必须要显示的声明,从而保证运行时是可用的引用。

    防止使用悬挂引用Dangling reference

    fn main() {
        {
            let r;
    
            {
                let x = 5;
                r = &x;
            }
    
            println!("r: {}", r);
        }
    }
    

    r 变量被赋值为x的引用,当试图打印r的时候,x变量已经超出了scope而被销毁,所有会有如下报错,提示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
    
    For more information about this error, try `rustc --explain E0597`.
    error: could not compile `chapter10` due to previous error
    
    

    借用检查

    Rust的借用检查borrow checker会检查所有范围确保借用是有效的

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

    上述代码中,r的生命周期范围是a,x的生命周期范围是b,明显a比b范围更大,r引用了一个变量x,而x的生命周期b并不足够长,也就是r会指向一个不可用的引用,所以Rust会报错提示x` does not live long enough

    改成如下即可

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

    修改后,x的生命周期b比a范围更大,保证了r指向的引用x会一直有效

    函数中的生命周期

    如下函数,返回更长的那个字符串

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

    编译不能通过,报错如下:缺少生命周期的定义

    $ cargo run
       Compiling chapter10 v0.1.0 (file:///projects/chapter10)
    error[E0106]: missing lifetime specifier
     --> src/main.rs:9:33
      |
    9 | fn longest(x: &str, y: &str) -> &str {
      |               ----     ----     ^ expected named 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`
    help: consider introducing a named lifetime parameter
      |
    9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
      |           ^^^^    ^^^^^^^     ^^^^^^^     ^^^
    
    For more information about this error, try `rustc --explain E0106`.
    error: could not compile `chapter10` due to previous error
    

    生命周期注解语法

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

    下面的代码,对参数x,y都定义在生命周期`a,所以无论返回x还是y,都是可见的,可以编译

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

    总结:生命周期的意义在于对入参和返回值的生命周期进行协调,确保他们符合规则,否则编译器报错,同时Rust编译器也有了足够的信息来保证内存的安全,避免悬挂指针,野指针等一些内存问题。

    struct中的生命周期

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

    生命周期缩写

    fn first_word(s: &str) -> &str {
        let bytes = s.as_bytes();
    
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &s[0..i];
            }
        }
    
        &s[..]
    }
    

    上面的函数没有生命周期参数,也能编译,原因是Rust编译器可以自行推导出来生命周期,其实相当于

    fn first_word<'a>(s: &'a str) -> &'a str {}
    

    函数或方法的参数生命周期,称为输入生命周期;
    返回值的生命周期,称为输出生命周期;

    规则:

    • 规则1:每个引用的入参,都有自己的生命周期
    • 规则2:如果只有一个输入生命周期参数,那么输出生命周期和输入相同
    • 规则3:入参中有&self,&mut self,则把self的生命周期赋给返回值

    方法的生命周期

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

    静态生命周期

    在程序的整个生命周期中一直有效

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

    完整的例子

    融合了traits bound,lifetime,generic的例子

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

    参考:

    1) https://blog.csdn.net/setlilei/article/details/120717780 比较eq, partialeq, Ord, PartialOrd

    相关文章

      网友评论

          本文标题:Rust编程语言-10-泛型,Traits,生命周期

          本文链接:https://www.haomeiwen.com/subject/sqfgcrtx.html