rust泛型、trait、生命周期、测试


提取函数

// 寻找最大值

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

fn main() {
    let number_list = vec![1,2,3,4,5,6,5,34,2,31];
    let result = find_largest(&number_list);
    println!("{}",result); //34
}

泛型

  • 提高代码复用能力
  • 是具体类型或其他属性的抽象代替
    • 编译器在编译时将“占位符”替换为具体的类型

函数泛型

demo:

// 寻找最大值

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

fn main() {
    let number_list = vec![1,2,3,4,5,6,5,34,2,31];
    let result = find_largest(&number_list);
    println!("{}",result); //34

    let number_list = vec!['a','c','d','b'];
    let result = find_largest(&number_list);
    println!("{}",result); //d

结构体泛型

struct Point<T> {
    x: T,
    y: T,
}

struct Point2<T,U> {
    x: T,
    y: U,
}

可以使用多个泛型的参数。

枚举泛型

enum Result<T, E> {
    OK(T),
    Err(E),
}

方法类型

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

也可以针对具体的类型实现方法。

泛型代码的性能

  • 和具体类型代码速度一样
  • 编译时会替换,单态化

Trait

告诉编译器某种类型具有哪些可以与其他类型共享的功能。

把方法签名放在一起,来定义满足目的必须的一组行为。

类似接口。

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

demo

lib.rs:

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

main.rs:

use my_project::{Tweet, Summary};

fn main() {
    let tweet = Tweet {
        username: "lhq".to_string(),
        content: "some content".to_string(),
        reply: false,
        retweet: false,
    };
    println!("{}",tweet.summarize()); // lhq:some content
}

实现trait的约束

  • 在某个类型上实现某个trait的前提是:
    • 类型或trait是本地crate定义的
  • 无法为外部类型实现外部trait
    • 确保其他人代码不能破坏自己的代码

默认trait实现

pub trait Summary {
    fn summarize_author(&self) -> String;
    
    fn summarize(&self) -> String {
        format!("default content: {}",self.summarize_author())
    }
}

trait作为参数

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

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

trait bound

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

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

太长可以简化为:

pub fn notify<T: Summary + Display, U: Clone + Debug>(a: T, b: U) {
    println!("Breaking news: {}",a.summarize())
}

// 简化版
pub fn notify2<T, U>(a: T, b: U) -> String
where 
    T: Summary + Display, 
    U: Clone + Debug,
{
    format!("Breaking news: {}",a.summarize())
}

返回trait类型

返回类型写成 impl summary就行了,但是返回类型的具体类型只能是一个,例如不能返回Tweet或者NewsArticle

生命周期

生命周期标注

  • 描述了多个引用的生命周期间的关系,但是不影响生命周期
  • 当指定了泛型生性周期函数,就可以接收带有任何声明周期的引用

语法

'开头,通常全小写。如'a

单个生命周期没有意义。

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

image-20221128145701257

`string2` does not live long enough
borrowed value does not live long enough

生命周期省略的三个规则

适用于fnimpl

  • 每个引用类型的输入参数都有自己的生命周期
  • 如果只有一个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期
  • 如果多个输入生命周期,但其中一个是&self或者&mut self,那么self的生命周期被赋给所有的输出生命周期

'static静态生命周期

整个程序的持续时间

测试

测试函数使用test属性进行标注

#[cfg(test)]
mod tests {
    #[test]
    fn haha() {
        assert_eq!(1+1, 2);
    }
}

// running 1 test
// test tests::haha ... ok

// test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

测试失败

  • panic就失败
  • 每个测试运行在一个新线程
  • 挂掉就失败

断言Assert

true测试通过,false就panic。

assert_eq!判断相等

assert_ne!判断不等

这两个会自动打印两个参数的值。

自定义错误信息

#[test]
fn another() {
    assert!(1+1==3,"计算错误了: {}","1+1")
    // thread 'tests::another' panicked at '计算错误了: 1+1', src\lib.rs:75:9
}

验证错误处理

should_panic

#[test]
#[should_panic]
fn another() {
    panic!("123")
}

为了更精确:

expected

#[test]
#[should_panic(expected = "123")]
fn another() {
    panic!("yoyoyo 123")
}

Result

#[test]
fn it_works() -> Result<(),String> {
    if 1+1 == 3 {
        Ok(())
    } else {
        Err(String::from("calculate err"))
    }
}

控制测试的行为

默认会并行多个测试:运行快,不存在依赖关系

控制线程数量:

cargo test --test-threads=1

显示函数输出:

如果想在成功的测试代码中看到打印的内容:

--show-output

按名称运行测试:

cargo test xx

或者指定测试名的一部分

忽略测试:

#[ignore]

测试的分类:

  • 单元测试

    #[cfg(test)]才编译和运行代码,cargo build不会

    rust允许测试私有函数

  • 集成测试

    测试多个部分能否一起正常工作

    tests目录,无需#[cfg(test)]

    cargo test 函数名

    cargo test 文件名