目录
一,Clone、Copy
1,基本类型
rust基本类型包括:
- 所有整数类型,比如
u32
- 布尔类型,
bool
,它的值是true
和false
- 所有浮点数类型,比如
f64
- 字符类型,
char
2,类型的Clone特征
拥有Clone特征的类型:
- 基本类型
- String
- 容器
- 显式声明Clone特征的结构体
没有Clone特征的类型:
- 没有显式声明Clone特征的结构体(结构体默认)
递归决定是否有Clone特征的类型:
- 元组,当且仅当其包含的类型都有Clone特征的情况下,其自身有Clone特征。
3,显式声明结构体的Clone特征
显式声明结构体的Clone特征的前提条件:当且仅当结构体中的成员都具有Clone特征的情况下,可以显式声明Clone特征。
#[derive(Clone)]
struct S{}
#[derive(Clone)]
struct P{
a:i32,
b:S,
}
4,类型的Copy特征
拥有Copy特征的类型:
- 基本类型
- 显式声明Copy特征的结构体
没有Copy特征的类型:
- String
- 容器
- 没有显式声明Copy特征的结构体(结构体默认)
递归决定是否有Copy特征的类型:
- 元组,当且仅当其包含的类型都有Copy特征的情况下,其自身有Copy特征。
5,显式声明结构体的Copy特征
一定是具有Clone特征的类型,才可能具有Copy:
pub trait Copy: Clone {
// Empty.
}
显式声明结构体的Copy特征的前提条件:当且仅当结构体中的成员都具有Copy特征的情况下,可以显式声明Copy特征。
#[derive(Clone,Copy)]
struct P{
a:i32,
}
fn main() {
let x:P=P{a:5};
let y=x;
assert_eq!(x.a,5);
}
5,变量和字面量的特征
字面量会自动推导出类型,所以变量和字面量都有唯一确定的类型。
变量和字面量是否具有clone和copy特征,完全取决于其类型是否具有。
6,特征总结
所有类型可以分为3类:
没有clone和copy特征,有clone没有copy特征,有clone和copy特征。
二,变量绑定、赋值
1,变量绑定:Clone拷贝场景
对于有clone特征的变量或字面量,可以调用clone函数进行拷贝而不转移所有权。
#[derive(Clone)]
struct P{
a:i32,
}
fn main() {
let x:P=P{a:5};
let y=x.clone();
assert_eq!(x.a,5);
}
2,变量绑定:Copy拷贝场景
如果let绑定语句的等号右边是一个有Copy特征的变量或字面量,那么这是一个拷贝行为。
let x = 5;
let xx = x;
assert_eq!(5, x);
assert_eq!(6, xx+1);
3,变量绑定:所有权转移场景
如果let绑定语句的等号右边是一个没有Copy特征的变量或字面量,那么这是一个所有权转移的行为。
错误代码:
let x = vec![1,2,3];
assert_eq!(x[0],1);
let y=x;
assert_eq!(x[0],1); // 错误
错误原因:y转移走了所有权,不能再使用x
4,转移的永久性
错误代码:
fn f(){
let x = "he".to_string();
if false{
let y = x;
}
println!("{}",x);
}
错误原因:y转移了x的所有权之后,x就再也不能用了,即使y的生命周期结束了也一样。
即使这里用if false包起来了,只要这部分代码能编译,y就转移走x的所有权。
5,赋值语句
无论是let语句,还是赋值语句,有Copy的就会Copy,没有Copy的就转移所有权。
PS:等号右边是形如x.clone()的,虽然不会转移x本身的所有权,但是会转移走clone函数返回值的所有权。
struct S{
x:i32
}
fn main() {
let c= S{x:1};
let mut d =S{x:2};
d=c;
assert_eq!(d.x, 1);
let mut x = Vec::from([1,2,3]);
let mut y = Vec::from([1,2,4]);
y=x;
assert_eq!(y[2], 3);
let x = 5;
let mut y = 6;
y=x;
assert_eq!(x, 5);
assert_eq!(y, 5);
}
三,引用、Borrow
1,对常量的引用
fn main() {
let x:P=P{a:6};
let y= & x;
assert_eq!(x.a,6);
assert_eq!(y.a,6);
assert_eq!((*y).a,6);
println!("end");
}
常量只有可读性,原变量x和引用变量y都持有读的能力。
这里y可以直接用,也可以先解引用再用。
2,对变量的不可变引用
正确代码:
struct P{
a:i32,
}
fn main() {
let mut x:P=P{a:6};
let y= &x;
assert_eq!(x.a,6);
assert_eq!(y.a,6);
assert_eq!((*y).a,6);
x.a=5;
assert_eq!(x.a,5);
println!("end");
}
变量x持有读写能力,不可变的引用y只有读能力。
错误代码:
struct P{
a:i32,
}
fn main() {
let mut x:P=P{a:6};
let y= &x;
x.a=5;
if false{
assert_eq!(y.a,5);
}
println!("end");
}
错误原因:在y的读行为结束之前,x不能执行写行为,否则会造成冲突。即使后面的y的读写包在了if false里面也是一样,因为编译器不做这种代码运行推导。
同一个变量可以引用多次,也可以对引用变量再进行引用:
struct P{
a:i32,
}
fn main() {
let mut x:P=P{a:6};
let y= &x;
let z=&x;
let z2=&z;
let z3=&z2;
let z4=&z3;
assert_eq!(x.a,6);
assert_eq!(y.a,6);
assert_eq!(z.a,6);
assert_eq!(z4.a,6);
assert_eq!((*z4).a,6);
assert_eq!((**z4).a,6);
assert_eq!((***z4).a,6);
assert_eq!((****z4).a,6);
println!("end");
}
这里的z4可以直接读成员,也可以解引用若干次再使用。
3,对变量的可变引用
正确代码:
struct P{
a:i32,
}
fn main() {
let mut x:P=P{a:6};
let y= &mut x;
assert_eq!(y.a,6);
y.a=5;
assert_eq!(x.a,5);
println!("end");
}
错误代码:
struct P{
a:i32,
}
fn main() {
let mut x:P=P{a:6};
let y= &mut x;
assert_eq!(x.a,6);
assert_eq!(y.a,6);
println!("end");
}
错误原因:y的读写行为结束之前,x不能执行读行为,否则会造成冲突。
可变引用和不可变引用不能同时存在,否则会造成冲突。
4,函数调用
错误代码:
fn fun(x:Vec<i32>)->i32{
x[0]+1
}
fn main() {
let x = vec![1,2,3];
assert_eq!(fun(x),2);
assert_eq!(x.len(), 3);
println!("end");
}
错误原因:函数调用时转移走了所有权。
正确代码:
fn fun(x:&Vec<i32>)->i32{
x[0]+1
}
fn main() {
let x = vec![1,2,3];
assert_eq!(fun(&x),2);
assert_eq!(x.len(), 3);
println!("end");
}
实现方式:函数入参改成引用类型,传参时也要改成引用。
5,Borrow、BorrowMut
泛型的Borrow特征比单纯的引用更加强大,比如可以引用数据内的部分数据,或者和指针相关的操作。
pub trait Borrow<Borrowed: ?Sized> {
#[stable(feature = "rust1", since = "1.0.0")]
fn borrow(&self) -> &Borrowed;
}
pub trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> {
#[stable(feature = "rust1", since = "1.0.0")]
fn borrow_mut(&mut self) -> &mut Borrowed;
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Borrow<T> for T {
#[rustc_diagnostic_item = "noop_method_borrow"]
fn borrow(&self) -> &T {
self
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> BorrowMut<T> for T {
fn borrow_mut(&mut self) -> &mut T {
self
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Borrow<T> for &T {
fn borrow(&self) -> &T {
&**self
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Borrow<T> for &mut T {
fn borrow(&self) -> &T {
&**self
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> BorrowMut<T> for &mut T {
fn borrow_mut(&mut self) -> &mut T {
&mut **self
}
}
四,引用总结
1,引用的生命周期
(1)一个引用变量的生命周期只到它的最后一次读写为止
(2)如果声明了引用之后没有读写,那么生命周期直接结束,但是这和直接删除这一句不一样,因为声明引用这一行相当于一次读操作。
(3)如果一个引用变量y被z引用了,且z最后一次读写比y的最后一次读写更晚,那么y的生命周期延长到z的最后一次读写。
PS:如果声明了z是对y的引用之后没有读写,那么声明的这一句就是z的最后一次读操作,这也可能延长y的生命周期。
讨论引用规则时我们默认只讨论一个生命周期之内的引用。
2,对字面量的引用
对字面量的引用,无论是可变引用还有不可变引用,其实都不是引用,而是copy拷贝,讨论引用规则时我们默认不把对字面量的引用这个当做引用。
3,对普通变量的引用
对于普通变量,有mut的是可变变量,没有mut的是不可变变量(常量)。
可变变量可以加可变引用,也可以加不可变引用,不可变变量只能加不可变引用。
4,对引用变量的引用
无论是可变引用变量还是不可变引用变量,都和普通变量一样,可能是可变变量也可能是不可能变量。
对引用变量加引用的规则,和对普通变量一致。
5,对同一变量的引用
对不可变变量不能加可变引用,可以加多个不可变引用。
对可变变量可以加唯一的可变引用,也可以加多个不可变引用。
即,可变引用存在的情况下,只能有一个引用。
6,链式引用
以y是对x的引用,z是对y的引用为例,更长的链的情况应该规则类似。
y的声明周期参考上文“引用的生命周期”。
在所有情况下,z对x的读写能力都和y对x的读写能力相同,因为z一直持有对y的读能力。
(1)x是不可变变量
x和y 一直有读能力,没有写能力
(2)x是可变变量
在y的最后一次读操作或写操作之前,x没有读写能力,之后,x有读写能力
y的能力在声明周期内不变,有读能力,有没有写能力取决于是可变引用还是不可变引用。
五,智能指针
0,相关通用知识
(1)deref
参考deref
智能指针往往是数据套一个外壳,用deref可以很方便的解引用或者说隐式转换。
(2)智能指针嵌套
智能指针嵌套之后,会产生deref隐式转换链,更加的自由多变。
(3)隐式转换导致拥有多个同名函数
比如borrow_mut函数,既有通用的泛型实现,又有智能指针本身的函数,有时可以通过限制use std::borrow::BorrowMut;生效的范围,来修改默认指向。
比如RefCell定义了borrow_mut函数,Rc没有,所以Rc<RefCell<T>>类型的数据既可以调用泛型的borrow_mut函数,也可以通过隐式转换去调用RefCell的borrow_mut函数,默认是前者,因为默认不发生隐式转换。
即,如果有use std::borrow::BorrowMut;这一句则默认调用泛型的borrow_mut函数,如果没有这一句,则只能调用RefCell的borrow_mut函数。
1,NonNull
(1)一句话含义
*mut T 但是非零且协变。
(2)源码解析
pub struct NonNull<T: ?Sized> {
pointer: *const T,
}
2,PhantomData
(1)一句话含义
零大小的类型,用来标记像是拥有一个 T
类型的数据。
(2)源码解析
pub struct PhantomData<T: ?Sized>;
3,UnsafeCell
(1)一句话含义
内部可变引用。
(2)具体含义
普通的&T和&mut T需要满足条件:读和写不能同时存在,写和写不能同时存在。
如果想突破这个限制,可以使用UnsafeCell
(3)用法示例
use std::cell::UnsafeCell;
fn main() {
let a : &UnsafeCell<i32>= &5.into();
let b=a;
unsafe{
*b.get()=6;
println!("{}",*a.get());
*a.get()=7;
println!("{}",*b.get());
}
println!("end");
}
输出6 7
这个例子演示的语义是,a和b持有同一个数据的可变引用。
(4)安全性
UnsafeCell转成可变引用是安全的,而解引用是不安全的,所以需要用unsafe包含起来。
为什么叫UnsafeCell?
因为共享引用同一个数据,数据是可变的。
(5)源码解析
定义:
#[lang = "unsafe_cell"]
#[stable(feature = "rust1", since = "1.0.0")]
#[repr(transparent)]
pub struct UnsafeCell<T: ?Sized> {
value: T,
}
其中宏lang = "unsafe_cell"告诉编译器对UnsafeCell进行特殊处理,具体的处理内容是不允许协变。
泛型方法:
pub const fn get(&self) -> *mut T {
self as *const UnsafeCell<T> as *const T as *mut T
}
pub const fn get_mut(&mut self) -> &mut T {
&mut self.value
}
4,Atomic*、内部可变性
5,Cell
(1)一句话含义
可变的内存位置。
(2)用法示例
use std::cell::Cell;
fn main() {
let x=Cell::new(1);
let y=x.get();
x.set(2);
assert_eq!(x.get(),2);
assert_eq!(y,1);
println!("end");
}
这个例子中,y拷贝的是整数值
fn main() {
let x=Cell::new(1);
let y=&x;
x.set(2);
assert_eq!(y.get(),2);
y.set(3);
assert_eq!(x.get(),3);
println!("end");
}
这个例子演示的语义是,x和y持有同一个数据的可变引用,和上面的UnsafeCell的例子差不多。
(3)get方法
pub fn get(&self) -> T {
// SAFETY: This can cause data races if called from a separate thread,
// but `Cell` is `!Sync` so this won't happen.
unsafe { *self.value.get() }
}
执行的是值拷贝,所以get相当于拷贝。
示例:
fn main() {
let x=std::cell::Cell::new((1,2));
x.get().0 =100;
print!("{:?};",x.get());
}
输出 (1, 2);
(4)源码解析
#[stable(feature = "rust1", since = "1.0.0")]
#[repr(transparent)]
pub struct Cell<T: ?Sized> {
value: UnsafeCell<T>,
}
6,RefCell
(1)一句话含义
具有动态检查借用规则的可变内存位置
(2)用法示例
use std::cell::RefCell;
use std::cell::RefMut;
let x:RefCell<i32>=RefCell::new(5);
let y:RefMut<i32> = x.borrow_mut();
assert_eq!(*y,5);
部分场景下(如结构体),可以省略解引用符,相当于deref做了隐式转换:
struct P{
a:i32,
}
fn main() {
let x:RefCell<P>=RefCell::new(P{a:5});
let y:RefMut<P> = x.borrow_mut();
assert_eq!((*y).a,5);
assert_eq!(y.a,5);
println!("end");
}
(3)源码解析
pub struct RefCell<T: ?Sized> {
borrow: Cell<BorrowFlag>,
borrowed_at: Cell<Option<&'static crate::panic::Location<'static>>>,
value: UnsafeCell<T>,
}
7,Mutex
(1)一句话含义
互斥锁,可用于保护共享数据
(2)源码解析
pub struct Mutex<T: ?Sized> {
inner: sys::Mutex,
poison: poison::Flag,
data: UnsafeCell<T>,
}
8,RwLock
(1)一句话含义
读写锁
(2)具体含义
RwLock最多同时有一个写操作和多个读操作。
(3)源码解析
pub struct RwLock<T: ?Sized> {
inner: sys::RwLock,
poison: poison::Flag,
data: UnsafeCell<T>,
}
9,Arc
(1)一句话含义
线程安全的引用计数指针
(2)源码解析
struct ArcInner<T: ?Sized> {
strong: atomic::AtomicUsize,
weak: atomic::AtomicUsize,
data: T,
}
pub struct Arc<T: ?Sized, A: Allocator = Global> {
ptr: NonNull<ArcInner<T>>,
phantom: PhantomData<ArcInner<T>>,
alloc: A,
}
10,RcBox
(1)一句话含义
RcBox是rc中的私有数据结构,不对外开放
(2)源码解析
struct RcBox<T: ?Sized> {
strong: Cell<usize>,
weak: Cell<usize>,
value: T,
}
11,Rc
(1)一句话含义
Rc 是单线程引用计数指针
(2)用法
Rc内的数据可以直接用解引用符来用,这是一种通用用法:
use std::rc::Rc;
struct P{
a:i32,
}
fn main() {
let x=Rc::new(P{a:5});
assert_eq!((*x).a,5);
assert_eq!(x.a,5);
println!("end");
}
部分场景下(如结构体),可以省略解引用符,相当于deref做了一次隐式转换
引用计数:
fn main() {
let x=Rc::new(1);
let y=Rc::clone(&x);
let y=Rc::clone(&x);
let y=Rc::clone(&x);
println!("{}",Rc::strong_count(&x));
}
输出4
每次clone增加1个引用计数,变量析构的时候减少1个引用计数。
(3)Rc的borrow
Rc又重新实现了Borrow特征
impl<T: ?Sized, A: Allocator> borrow::Borrow<T> for Rc<T, A> {
fn borrow(&self) -> &T {
&**self
}
}
这个函数和泛型的borrow是同名的,所以需要这么调用:
let mut x = Rc::new(42);
let y:&Rc<i32> = x.borrow();
let z:&i32 = x.borrow();
let y=<Rc<i32> as Borrow<Rc<i32>>>::borrow(&x);
let z=<Rc<i32> as Borrow<i32>>::borrow(&x);
如果简单的写成:
let y = x.borrow();
这是编译不过的。
这种语法和c++是不一样的,c++不能根据返回值类型进行推导重载函数,这里的推导差不多可以理解成根据返回值类型进行推导,当然,也可以理解成一种更复杂的类似于SFINAE的机制,即有且只有一种情况能编译成功的情况下就按照这种情况进行编译。
PS:
Rc没有重新实现borrow_mut,所以没有同名冲突。
因为Rc是引用计数,设计场景用于多个变量持有同一数据的所有权,所以不提供可变引用。
(4)源码解析
pub struct Rc<
T: ?Sized,
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
> {
ptr: NonNull<RcBox<T>>,
phantom: PhantomData<RcBox<T>>,
alloc: A,
}
12,UniqueRc
(1)源码解析
pub struct UniqueRc<T> {
ptr: NonNull<RcBox<T>>,
phantom: PhantomData<RcBox<T>>,
}
13,Box
(1)源码解析
use std::boxed::Box;
let five = Box::new(5);
assert_eq!(*five, 5);
14,ThinBox
(1)源码解析
struct WithOpaqueHeader(NonNull<u8>);
pub struct ThinBox<T: ?Sized> {
ptr: WithOpaqueHeader,
_marker: PhantomData<T>,
}
六,智能指针搭配使用
1,Arc+Mutex
用法示例
let mutex = Arc::new(Mutex::new(0));
let c_mutex = Arc::clone(&mutex);
thread::spawn(move || {
*c_mutex.lock().unwrap() = 10;
}).join().expect("thread::spawn failed");
assert_eq!(*mutex.lock().unwrap(), 10);
这个例子中,一个线程中修改了共享值,另外一个线程也会拿到新的值。
更复杂的例子:
fn main() {
let mutex = Arc::new(Mutex::new(0));
let c_mutex = Arc::clone(&mutex);
thread::spawn(move || {
*c_mutex.lock().unwrap() = 10;
}).join();
match mutex.lock(){
Ok(data)=>{
println!("{}",data);
}
Err(_)=>{
println!("error");
}
};
let mutex = Arc::new(Mutex::new(0));
let c_mutex = Arc::clone(&mutex);
thread::spawn(move || {
let mut data=c_mutex.lock().unwrap();
*data = 10;
}).join();
match mutex.lock(){
Ok(data)=>{
println!("{}",data);
}
Err(_)=>{
println!("error");
}
};
let mutex = Arc::new(Mutex::new(0));
let c_mutex = Arc::clone(&mutex);
thread::spawn(move || {
let mut data=c_mutex.lock().unwrap();
*data = 10;
panic!();
}).join();
match mutex.lock(){
Ok(data)=>{
println!("{}",data);
}
Err(_)=>{
println!("error");
}
};
}
这个例子中,分别输出10 10 error
因为panic导致RAII无法正常运行,导致锁没有释放。
2,Arc+RwLock
fn main() {
let mutex = Arc::new(RwLock::new(0));
let mutex_ref = mutex.clone();
let _=thread::spawn(move||{
let mut data = mutex_ref.write().unwrap();
*data+=1;
}).join();
let data1 = mutex.read().unwrap();
let data2 = mutex.read().unwrap();
print!("{}{}", data1, data2);
}
输出 1 1
七,指针总结
rust一共四类指针:引用、胖指针、智能指针、原始指针
1,引用
参考上文
2,胖指针
即指向切片的指针,因为既有地址,又有长度所以叫胖指针。(类似c++的数组和指针差不多,但是数组有长度)
struct S{
x:i32
}
fn main() {
let mut c = [1,2,3,4,5,6];
let slice = &mut c[1..3];
slice[0]=9;
assert_eq!(c,[1,9,3,4,5,6]);
}
这个分类项有争议,因为胖指针可以理解为普通指针,即slice是指向数组[2,3]的普通指针,而数组本身是具有长度的。
3,智能指针
参考上一章
4,原始指针、unsafe块
可以把一个引用类型,强转成一个原始指针:
fn main() {
let x = 5;
let p = &x as *const i32;
unsafe {
assert_eq!(*p, 5);
}
println!("end");
}
这里把&x这个&i32类型的数据,转换成了*const i32类型的数据。
*p是解引用,原始指针解引用必须放在unsafe块中。
fn main() {
let mut x = 5;
let p = &mut x as *mut i32;
unsafe {
assert_eq!(*p, 5);
}
println!("end");
}
&是不可变引用,&mut是可变引用,类似的,*mut 是可变量指针,*const是常量指针。
转换规则:不可变引用只能转换成常量指针,可变引用可以转换成常量指针或可变量指针。
八,ref
fn main() {
let x=100;
let ref a:&i32=&x;
let b:&&i32=a;
let &a:&i32=&x;
let b:i32=a;
}
let语句的冒号后面写的类型,要把&和ref和变量本身的类型都带上。
当我们要引用一个Option的内部成员,可以用ref
struct P{
a:i32,
}
struct Node{
x:Option<P>,
}
fn main() {
let mut p = Node{x:Some(P{a:1})};
if let Some(ref mut x)=p.x{
x.a=2;
}
if let Some(y)=p.x{
assert_eq!(y.a,2);
}
println!("end");
}
这里的ref mut x得到的x的类型其实就是*mut P类型。