1.trait可以理解为接口,可以为类型(如结构体)定义方法,实现调用。
2.先声明的方式,pub trait Summary{fn summarize(&self)->String;}没有具体实现
实现需要 impl Summary for NewArticle{}
3.外部调用trait时是use demo::Sammary,demo是项目的名字,而不是文件的名字。
4.默认实现,类似初始化吧,很容易。
5.把trait作为一个参数传给一个函数,
可以用pub fn notify(item : impl Summary+std::fmt::Display){item.summarize()}
还可以用pub fn notify
where子句:pub fn notify
where
T:Summary+Display,
U:Clone+Debug,
{}
1.智能指针实现两个trait
一个是Deref trait:这个是允许智能指针struct的实例像引用一样使用。
另一个是Drop trait:这个是允许你自定义当智能指针实例离开作用域的代码。
2.Box是最简单的智能指针,两个结构,在堆上存储数据,在栈上是指向heap数据的指针。
实现了deref和drop两个trait。
Box的几种使用场景:1.在编译时,某类型的大小无法确定,但使用该类型的时候,上下文却要知道它的确切大小。
2.当你有大量的数据,想移交所有权,但是确保数据不会被复制。
3.当使用某个值时,只关心它实现的trait,而不关心它的具体类型。
let b=Box::new(5),离开作用域时,会释放heap上的数据以及存放在stack上的指针。
3.使用Box赋能递归类型
在编译时需要知道每一个类型所占的空间大小,而递归类型的大小无法在编译时确定。
Cons List里每个成员由两个元素组成,当前项的值和下一个元素,最后一个成员只包含一个Nil值,没有下一个元素。枚举之中,大小是最大的那个的大小。
use crate::List::{Cons,Nil};
fn main(){
let list=Cons(1,Box::new(Cons(2,Box::new(Cons(3,Box::new())))));}
enum List{Cons(i32,Box),Nil,}
Box只提供了“间接”存储和heap内存分配的功能,没有性能开销和额外的功能。
4.Deref trait
实现Deref trait 使我们可以自定义引用运算符*的行为。通过Deref,智能指针可以像常规引用一样来处理。
let x=5; let y=&x; assert_eq!(5,x); assert_eq!(5,*y);
impl Deref for MyBox{
type Target=T;
fn deref(&self)->&T{
&self.0}
}
之后的assert_eq!(5,y)就相当于(y.deref()).
5.函数和方法的隐式解引用转化(Deref Coercion)
是为函数和方法提供的一种便携特性。
假设T实现了Deref trait:Deref Coercion可以把T的引用转化为T经过Deref操作后生成的引用。
当把某类型的引用传递给函数或方法时,但它的类型与定义的参数类型不匹配,Deref Coercion就会自动发生,编译器会对deref进行一系列调用,转化为合适的类型,在编译时完成,没有额外的性能开销。
fn hello(name:&str){println!(“hello,{}”,name)}
fn main(){
let m=MyBox::new(String::from(“Rust”)) ;
hello(&m);hello(“Rust”)这样传才是正确的。或者这样传hello(&(*m)[…])
///这里解释如下:&m就是&MyBox,由于MyBox实现了Deref,所以转化为&String,而String类型又实现了Deref,所以返回&str
}
6.Drop trait
实现Drop trait ,可以让我们自定义当值要离开作用域时发生的动作。无非就是释放资源。
struct CustomSmartPointer{data:String,}
impl Drop for CustomSmartPointer{
fn drop(&mut self)
{println!(“Drop data {}”,self.data);}}
fn main(){
let c=CustomSmartPointer{data:String::from(“my stuff”)}
}
不需要进行导入,因为Drop是在预导入模块里面的,可以理解为,在}之后,离开作用域会自动进行drop()方法。drop的顺序是相反的。
如果想要提前释放资源,可以通过std::mem::drop来进行drop,当然也可以不导入,这都是默认导入的。drop©,不会出现多重释放。
7.Rc引用计数智能指针(reference counting)
有时候一个值会有多个所有权,为了支持多重所有权,就是个计数器,有几个所有权计数就是几,drop了之后就会减一。到0个引用的时候,就会清理掉这个值。
Rc使用场景:需要在heap上分配数据,这些数据被程序的多个部分读取(只读),但在编译时无法确定哪个部分最后使用完这些数据。
Rc只能用于单线程场景。Rc不在预导入模块(prelude)。
Rc::clone(&a)函数:增加引用计数。Rc::strong_count(&a):获取引用计数。
例子:两个List共享另一个List的所有权。
enum LIst{Cons(i32,Rc),Nil}
use crate::List::{Cons,Nil};
use std::rc::Rc;
fn main(){
let a=Rc::new(Cons(5,Rc::new(Cons(10,Rc::new(Nil)))));//计数器为1,可以通过Rc::strong_count(&a)进行引用计数的查看。
let b=Cons(3,Rc::clone(&a));//计数器为2
let c=Cons(4,Rc::clone(&a));//计数器为3
}
a.clone()和Rc::clone()的区别:a.clone()是深度拷贝,速度较慢,相当于数据有两份。Rc::clone()只是将指针指向,并没有复制数据,速度比较快。
Rc是不可变的引用,只能只读。否者会引起数据冒险。
8.RefCell和内部可变性
内部可变性(interior mutability):内部可变性是Rust的设计模式之一,允许你在支持有不可变引用的前提下对数据进行修改(使用unsafe)。
借用规则在不同阶段进行检查的比较:编译阶段:尽早暴露问题,不会有任何运行时开销。而在运行时检查,可以实现某些特定的内存安全场景(不可变环境修改数据)。
选择Box、Rc和RefCell的依据:
Box同一个数据的所有者只能有一个,可变性、借用检查:可变、不可变借用(编译时检查)
Rc同一个数据的所有者可以有多个,可变性、借用检查:不可变借用(编译时检查)
RefCell同一个数据的所有者只能有一个,可变性、借用检查:可变、不可变借用(运行时检查)
使用RefCell在运行时记录借用信息:
两个方法(安全接口):
borrow方法:返回智能指针Ref,它实现了Deref。
borrow_mut方法:返回智能指针RefMut,它实现了Deref。
RefCell会记录当前存在多少个活跃的Ref和RefMut智能指针。
每次调用borrow:不可变借用计数加一。任何一个Ref的值离开作用域被释放时:不可变借用计数减一。
每次调用borrow_mut:可变借用计数加一。任何一个RefMut的值离开作用域被释放时:可变借用计数减一。
以此技术来维护借用检查规则:任何时间里,只允许拥有多个不可变借用或一个可变借用。当我们违背这个借用规则的时候,这个RefCell就会在运行时来触发panic。
个人认为这玩意就是为了不在编译时不通过,对于一些不知道正确与否的不安全代码,出错才会panic,否者可以通过。
将Rc和RefCell结合使用,来实现一个拥有多重所有权的可变数据:
enum LIst{Cons(Rc
use crate::List::{Cons,Nil};
use std::rc::Rc;
fn main(){
let value=Rc::new(RefCell::new(5));//value是个指针,指向5
let a=Rc::new(Cons(Rc::clone(&value),Rc::new(Nil)));//一个列表a,指向5
let b=Cons(Rc::new(RefCell::new(6)),Rc::clone(&a));//一个列表b,自己有一个可变元素6,尾巴指向a列表.
let c=Cons(Rc::new(RefCell::new(10)),Rc::clone(&a));
value.borrow_mut()+=10; //自动解引用value为Rc,调用borrow_mut()方法之后,变成RefMut,再解引用加10.
}
9.循环引用可以导致内存泄露
例如使用Rc和RefCell就可能创造出循环引用,从而发生内存泄露:每个项的引用数量不会变成0,值也不会被处理掉。
如何防止内存泄露的解决办法:1.依靠开发者来保证,不能依靠Rust。2.重新组织数据结构:一些引用来表达所有权,一些引用不表达所有权。
防止循环引用,把Rc换成Weak,通过Rc:downgrade方法创建,weak_count来追踪存在多少个Weak,当Strong Reference为0的时候
Weak reference会自动断开。
1.编写和运行测试
测试函数体通常执行3个操作(3A):准备数据/状态(arrange),运行被测试的代码(action),断言(assert)结果。
在函数上加上#[test],把函数变成测试函数。直接运行cargo test即可。
测试函数panic就表示失败,每个测试运行在一个新线程,当主线程看见某个测试线程挂掉了,那个测试就标记为失败。
2.assert!宏,来自标准库,用来确定某个状态是否为true,true为通过,false则调用panic失败。
补充一个关于结构体方法的调用,
impl Rectangle { fn wider(&self, rect: &Rectangle) -> bool {
self.width > rect.width
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 40, height: 20 };
println!("{}", rect1.wider(&rect2));//前面的是调用self,方法里面的参数是参数。带引用&
}
在测试的例子中,assert!(larger.can_hold(&smaller));
还有两个用来测试相等性的assert_eq!()和assert_ne!(),两个宏。如果断言失败,会打印出两个参数的值。
3.自定义错误信息
assert!:第一个参数必填,第二个参数为自定义信息。
assert_eq!和assert_ne!:前两个参数必填,第三个参数为自定义信息。自定义信息被传递给format!()宏,进行格式化,format和println有区别,一个是格式化,一个是打印。
pub fn greeting(name:&str)->Stirng{format!(“Hello,{}”,name)}
let result=greeting(“peter”);
4.should_panic属性:#[should_panic(expected=“Gusss”)]
函数panic:测试通过。函数没有panic:测试失败。
在测试中使用Result
fn it_works()->Result<(),String>{
if 2+2==4{Ok(())}
else{Err((String::from(“not eq”)))}
5.控制测试运行
最好的方法就是添加命令行的参数,默认的是并行运行所有的测试,不显示所有的输出。
并行运行测试:默认使用多个线程并行运行,确保测试之间不会互相依赖,不依赖于某个共享状态。
可以使用cargo test – --test-threads=1,表示单线程不用并行的方式运行。
显示函数输出,比如如果测试通过,不会在终端看见println!打印的内容。
如果想在成功的测试中看见打印的内容:使用cargo test – --show-output.
6.按名称运行测试子集
选择运行的测试:将测试的名称(一个或者多个)作为cargo test的参数。
运行单个测试:指定测试名。运行多个测试:指定测试名的一部分(部分模块名也行)。
忽略测试:只要加个#[ignore],可以将比较耗时的函数忽略。运行被忽略的测试:cargo test – --ignored
7.测试的分类
单元测试:小、专注,可测试private接口。
集成测试:在库外部,和其他外部代码一样使用你的代码。只能使用public接口,可能使用到多个模块。
#[cfg(test)]标注,只有运行cargo test才编译和运行代码,运行cargo build则不会。集成测试不需要标注。cfg就是配置的意思。
集成测试的目的是测试测试库的多个部分是否在一起可以正常工作。完全在外部。
创建tests文件夹,将被测试的库导入,在里面写测试函数。指定集成测试:cargo test --test 函数名。
grep(global regular expression print)
详细的见CSDN
1.迭代器模式就是对一系列项执行某些任务,迭代器负责遍历每个项以及确定序列遍历何时完成。
Rust的迭代器是懒惰的,除非你调用消费迭代器的方法,否则迭代器本身没有任何效果。
let mut v1_iter=v1.iter();
iterator trait:所有的迭代器都实现了iterator trait,定义于标准库。实现iterator trait需要你定义一个item类型,它用于next方法的返回类型。
iterator trait 仅要求实现一个方法next,next:每次返回迭代器的一项,返回结果包裹在Some里,迭代结束会返回None。
assert_eq!(v1.iter.next(),Some(&1));
几个迭代方法:iter方法:在不可变引用上创建迭代器,这里指的是元素是不可变的。
into_iter方法: 创建的迭代器会被移动到新的作用域,会获得所有权。
iter_mut方法:迭代可变的引用,元素是可变的。
2.消耗迭代器的方法,有一些方法会调用next方法,next方法就是消耗型的,最终会把整个迭代器消耗尽。
例如sum方法就会耗尽迭代器:sum会取得迭代器的所权有,通过反复调用next,遍历所有的元素。let total:i32=v1_iter.sum();
产生其他迭代器的方法,定义在iterator trait上的另外一些方法叫做“迭代器适配器”:把迭代器转换为不同的迭代器类型。
例如map就是接受一个闭包,作用于每一个元素,产生一个新的迭代器。let v1:Vec=vec![1,2,3]; v1.iter().map(|x| x+1);这样不行,因为不是消耗型。
使用 let v2:Vec<_>=v1.iter().map(|x| x+1).collect(); collect就是一个消耗型的适配器,把结果收集到一个集合中。
3.使用闭包捕获环境
filter方法:接收一个闭包,这个闭包在遍历迭代器的每个元素的时候,返回bool类型。
如果闭包返回true:当前元素将会在包含在filter产生的迭代器中。
如果闭包返回false:当前元素将不会包含在filter产生的迭代器中。
Shoe为一个结构体类型。
fn shoes_in_my_size(shoes:Vec,shoe_size:u32)->Vc{
shoes.into_iter().filter(|x| x.size==shoe_size).collect()}
4.使用iterator trait来创建自定义迭代器
实现next方法。
使用迭代器的效率高于循环,零成本抽象,编译耗时换来的是高性能。循环改迭代器也是一种优化策略。