泛型

使用<T>来表示使用泛型,这块和golang相差不大


struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' };

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

traits 特征

这个类似其他语言中的接口(interface)。比较特别的地方是可以在这个特征中实现具体的函数,简化这个特征的实现。


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

函数入参可以直接指定特征


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

另外一种更加省力的写法(称为a trait bound)


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

如果一个入参需要有多个特征,使用+来指定


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

生命周期

这块稍微看的有点晕。简单说就是有时候写的代码会存在一些回收的歧义,需要指定生命周期来让编译器能够编译通过。


// 这里x、y都有可能返回,编译器无法进行判断(什么时候进行回收)
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

为了编译通过,需要指定生命周期,一般用'a来指定

// x、y、返回值有相同的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

使用'static来指定整个程序生命周期

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

汇总

把上述三个特性都整合在一起,一个函数会像下面这个样子(有点酷):

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