当前位置: 首页 > news >正文

Rust编程学习 - 如何理解Rust 语言提供了所有权、默认move 语义、借用、生命周期、内部可变性

注意:&*两个操作符连写跟分开写是不同的含义。以下两种写法是不同的:

fn joint(){let s =Box::new(String::new());let p=&*s;println!("{}{}",p,s);
}
fn separate(){let s =Box::new(String::new());let tmp =*s;let p=&tmp;println!("{}{}",p,s);
}
fn main(){joint();separate();
}

fn joint() 是可以直接编译通过的,而fn separate()是不能编译通过的。因 为编译器很聪明,它看到&*这两个操作连在一起的时候,会直接把&s 表达式理解为 s.deref(), 这时候p 只是s 的一个借用而已。而如果把这两个操作分开写,会先执行s 把内部的数据move 出来,再对这个临时变量取引用,这时候s 已经被移走了,生命周期已 经结束。

同 样 的 ,let p =&{*s};这种写法也编译不过。这个花括号的存在创建了一个临时 的代码块,在这个临时代码块内部先执行解引用,同样是move 语 义 。

从这里我们也可以看到,默认的“取引用”、“解引用”操作是互补抵消的关系,互为逆 运算。但是,在Rust 中,只允许自定义“解引用”,不允许自定义“取引用”。如果类型有 自定义“解引用”,那么对它执行“解引用”和“取引用”就不再是互补抵消的结果了。先& 后 * 以 及 先 * 后 & 的 结 果 是 不 同 的 。


有时候需要手动处理

如果智能指针中的方法与它内部成员的方法冲突了怎么办呢?编译器会优先调用当前最 匹配的类型,而不会执行自动deref,在这种情况下,我们就只能手动deref来表达我们的需 求了。

比 如 说 ,Rc 类型和String类型都有clone方法,但是它们执行的任务不同。Rc::clone()做的是把引用计数指针复制一份,把引用计数加1。String::clone()做的是把字符串深复制一份。示例如下:

use std::rc::Rc;
use std::ops::Deref;
fn type_of(_:()){}
fn main(){let s =Rc::new(Rc::new(String::from("hello")));let s1 =s.clone(); //(1)//type_of(s1);let    ps1    =(*s).clone();      /1(2)//type_of(ps1);let pps1 =(**s).clone(); /1(3)  //type_of(pps1);
}

在以上的代码中,位置(1)处s1 的类型为Rc<Rc>,位 置 ( 2 ) 处ps1 的 类型为Rc,位置(3)处pps1 的类型为String。一般情况下,在函数调用的时候,编译器会帮我们尝试自动解引用。但在某些情况下, 编译器不会为我们自动插入自动解引用的代码。以String 和 &str 类型为例,在match表 达式中:

fn main(){let s =String::new();match  &s{""=>{}_=>{}}
{

这段代码编译会发生错误,错误信息为:

mismatched types:
expected &collections::string::String`,
found'&'static str

match后面的变量类型是&String, 匹配分支的变量类型为&'static str,这种情 况下就需要我们手动完成类型转换了。手动将&String 类型转换为&str 类型的办法如下。

  • 1)matchs.deref()。这个方法通过主动调用deref() 方法达到类型转换的目的。 此时我们需要引入 Deref trait方可通过编译,即加上代码usestd::ops::Deref;。

  • 2)match &s。我们可以通过s 运算符,也可以强制调用deref() 方法,与上面的 做法一样。

  • 3)match s.as_ref()。这个方法调用的是标准库中的std::convert::AsRef方法,这个trait 存在于prelude中,无须手工引入即可使用。

  • 4)match s.borrow()。这个方法调用的是标准库中的std::borrow::Borrow方法。要使用它,需要加上代码use std::borrow::Borrow;。

  • 5)match &s[ ·.]。这个方案也是可以的,这里利用了String重载的Index操作。


智能指针

Rust 语言提供了所有权、默认move 语义、借用、生命周期、内部可变性等基础概念。 但这些并不是Rust 全部的内存管理方式,在这些概念的基础上,我们还能继续抽象、封装更 多的内存管理方式,而且保证内存安全。

引 用 计 数

到目前为止,我们接触到的示例中都是一块内存总是只有唯一的一个所有者。当这个变 量绑定自身消亡的时候,这块内存就会被释放。引用计数智能指针给我们提供了另外一种选 择:一块不可变内存可以有多个所有者,当所有的所有者消亡后,这块内存才会被释放。

Rust 中提供的引用计数指针有std::rc::Rc类型和std::sync::Arc类型。 Rc 类型和Arc 类型的主要区别是: Rc 类型的引用计数是普通整数操作,只能用在单线程 中 ;Arc 类型的引用计数是原子操作,可以用在多线程中。这一点是通过编译器静态检查保 证的。Arc 类型的讲解可以参见第四部分相关章节,本章主要关注Rc 类型。

首先我们用示例展示 Rc 智能指针的用法:

use std::rc::Rc;
struct Sharedvalue {value:i32
}
fn main(){let shared_value :Rc<Sharedvalue>=Rc::new(SharedValue{value :42 });let owner1 =shared_value.clone();let owner2 =shared_value.clone();println!("value :{}{}",owner1.value,owner2.value);println!("address :{:p}{:p}",&owner1.value,&owner2.value);
}

编译运行,结果显示:

$./test
value:4242
address :0x13958abdf200x13958abdf20

这说明,owner1 owner2 里面包含的数据不仅值是相同的,而且地址也是相同的。这 正是Rc 的意义所在。从示例中可以看到,Rc 指针的创建是调用Rc::new 静态函数,与Box 类型一致(将来 会允许使用box 关键字创建)。如果要创建指向同样内存区域的多个Rc 指针,需要显式调用 clone 函数。请注意,Rc 指针是没有实现Copy trait的。如果使用直接赋值方式,会执行 move 语义,导致前一个指针失效,后一个指针开始起作用,而且引用计数值不变。如果需要创造新的Rc 指针,必须手工调用clone()函数,此时引用计数值才会加1。当某个Rc 指 针失效,会导致引用计数值减1。当引用计数值减到0的时候,共享内存空间才会被释放。

这没有违反我们前面讲的“内存安全”原则,它内部包含的数据是“不可变的”,每个 Rc 指针对它指向的内部数据只有读功能,和共享引用&一致,因此,它是安全的。区别在 于,共享引用对数据完全没有所有权,不负责内存的释放,Rc 指针会在引用计数值减到0的 时候释放内存。Rust 里面的Rc 类型类似于C++ 里面的shared_ptr类型, 且强制不可为空。

从示例中我们还可以看到,使用Rc 访问被包含的内部成员时,可以直接使用小数点语 法来进行,与T &T Box类型的使用方法一样。原因我们在前面已经讲过了,这是因为 编译器帮我们做了自动解引用。我们查一下Rc 的源码就可以知道:

impl<T:?Sized>Deref for Rc<T>{
type Target =T;
#[inline(always)]fn deref(&self)->&T{&self.inner().value}
}

可 见 ,Rc 类型重载了“解引用”运算符,而且恰好Target 类型指定的是T。这就意味 着编译器可以将Rc 类型在必要的时候自动转换为&T类型,于是它就可以访问T 的成员 变量,调用T 的成员方法了。因此,它可以被归类为“智能指针”。

下面我们继续分析Rc 类型的实现原理。它的源代码在src/liballoc/rc.rs中 ,Rc 类型的定义如下所示:

pub struct Rc<T:?Sized>{
_ptr:Shared<RCBox<T>>, }

其中RCBox 是这样定义的:

struct RcBox<T:?Sized>{strong:Cell<usize>,weak:Cell<usize>,value:T,
}

其中Shared 类型我们暂时可以不用管它,当它是一个普通指针就好。目前它还没有稳定,后续可能设计上还会有变化,因此本书就不对它深究了。

同时,它实现了clone 和 Drop 这两个trait 。在clone 方法中,它没有对它内部的数据 实行深复制,而是将强引用计数值加1,如下所示:

impl<T:?Sized>Clone   for   Rc<T>{
#[inline]
fn clone(&self)->Rc<T>{self.inc_strong();Rc{ptr:self.ptr }}
}
fn inc_strong(&self){self.inner().strong.set(self.strong().checked_add(1).unwrap_or_else(||unsafe{abort()}));
}

在 drop 方法中,也没有直接把内部数据释放掉,而是将强引用计数值减1,当强引用 计数值减到0的时候,才会析构掉共享的那块数据。当弱引用计数值也减为0的时候,才说 明没有任何Rc/Weak 指针指向这块内存,它占用的内存才会被彻底释放。如下所示:

unsafe impl<#[may_dangle]T:?Sized>Drop  for  Rc<T>{
fn drop(&mut self){unsafe{ let ptr =self.ptr.as_ptr();self.dec_strong();if self.strong()==0{//destroy the contained objectptr::drop_in_place(self.ptr.as_mut());//remove the implicit"strong weak"pointer now that we've //destroyed the contents.self.dec_weak();if self.weak()==0{Heap.dealloc(ptr as *mut u8,Layout::for_value(&*ptr)); }
}

从上面代码中我们可以看到,Rc 智能指针所指向的数据,内部包含了强引用和弱引用的计数值。这两个计数值都是用Cell 包起来的。为什么这两个数字一定要用Cell 包 来呢?我们假设,如果不用Cell, 而是直接用usize的话,在执行clone方法时会出现什么情况。

http://www.dtcms.com/a/577589.html

相关文章:

  • 自学建立网站网络品牌推广费用
  • 卑鄙的网站开发公司网站地图页面模板
  • php网站如何编辑WordPress图片一行多张
  • 学Java第四十一天-------查找算法和排序算法
  • 从0到1学习Qt -- 信号和槽(二)
  • AI、闪购、造车……双十一的第十七年,京东、阿里、美团还有“新活”
  • IDEA不切换当前分支,实现跨分支合并的终极方案
  • 法考资源合集
  • Redis(四)——事务
  • 便宜的vps租用网站有哪些网站使用ftp
  • TestKeyDownBit函数和SetKeyDownBit函数和ClearKeyDownBit函数分析
  • RHCSA---权限管理
  • Flutter for HarmonyOS开发指南(二):混合开发架构与通信机制
  • 分布式分片执行原理解析
  • 自主建站全攻略:建站系统的选择指南与深度说明
  • 什么网站有做qq群排名的关键词优化价格
  • 网站内容怎么进行编写
  • SSM 房屋租赁系统
  • 网站排名优化服务公司福建省住房和城乡建设网站
  • 开发集成热门小游戏(vue+js)
  • Java-简单项目开发流程
  • 莱芜网站优化平台现在中型公司做网站用的是什么框架
  • 区块链-B站API程序系统方案
  • LVS三种模式及调度算法解析
  • MySql 9.5.0(2025)-Windows安装步骤
  • 查看 MySQL 数据库里的所有信息,包括表结构、数据内容
  • Rocky10 使用kubeadm 安装k8s 单节点
  • K8s 中的Serviceaccount
  • MySQL 事务的两种使用方式
  • 【MySQL 进阶】高性能优化