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

泛型 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(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(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(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(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

pub fn notify(item1: &impl Summary, item2: &impl Summary) {} //item1和item2可能是不同的类型

pub fn notify(item1: &T, item2: &T) {} //同一种类型

Mutiple Trait Bound:多重约束

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

pub fn notify(item: &T) {}

在约束中使用where

fn some_function(t: &T, u: &U) -> i32 {

fn some_function(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

fn largest(list: &[T]) -> T {} //编译失败!

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

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

    largest
}

改进largest方法,返回&T

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

struct Pair {
    x: T,
    y: T,
}

impl Pair {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl Pair {
    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 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,生命周期)