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
}
}
`string2` does not live long enough
borrowed value does not live long enough
生命周期省略的三个规则
适用于fn
和impl
- 每个引用类型的输入参数都有自己的生命周期
- 如果只有一个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期
- 如果多个输入生命周期,但其中一个是
&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 文件名