Rust 所有权机制原理深入剖析

目录
  • what's ownership?
  • Scope (作用域)
  • ownership transfer(所有权转移)
    • move
    • clone
    • copy
  • References and Borrowing(引用和借用)
    • Mutable References(可变引用)
    • Dangling References(悬垂引用)

what's ownership?

常见的高级语言都有自己的 Garbage Collection(GC)机制来管理程序运行的内存,例如 Java、Go 等。而 Rust 引入了一种全新的内存管理机制,就是 ownership(所有权)。它在编译时就能够保证内存安全,而不需要 GC 来进行运行时的内存回收。

在 Rust 中 ownership 有以下几个规则:

  • 每个值都有一个 woner(所有者)
  • 在同一时间,每个值只能有一个 owner
  • 当 owner 离开作用域,这个值就会被丢弃

Scope (作用域)

通过作用域来划分 owner 的生命周期,作用域是一段代码的范围,例如函数体、代码块、if 语句等。当 owner 离开作用域,这个值就会被丢弃

example:

fn main() {
    let s = String::from("hello"); // 变量 s 进入作用域,分配内存
    // s 在这里可用
} // 函数体结束,变量 s 离开作用域,s 被丢弃,内存被回收

ownership transfer(所有权转移)

和大多数语言一样,Rust 在栈上分配基本类型的值,例如整型、浮点型、布尔型等。而在堆上分配复杂类型的值,例如 String、Vec 等。所以,这里就引入了两个概念,moveclone

move

move 操作会将变量的所有权转移给另一个变量,这样原来的变量就不能再使用了。这里需要注意的是,move 操作只会发生在栈上的值,因为在堆上的值是不可复制的,所以只能通过 clone 操作来复制。

example:

fn main(){
    let s1 = String::from("hello");
    let s2 = s1;
    print!("s1 = {}, s2 = {}", s1, s2);
}

在上面的代码例子中,如果你执行就会在编译时报错:

  --> src/main.rs:11:32
   |
9  |     let s1 = String::from("hello");
   |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
10 |     let s2 = s1;
   |              -- value moved here
11 |     print!("s1 = {}, s2 = {}", s1, s2);
   |                                ^^ value borrowed here after move

编译器提示我们,s1 在赋值给 s2 时发生了 move 的操作,它把字符串 hello 的所有权移交给了 s2,此时 s1 的作用域到这里就结束了,所以后面再使用 s1 就会报错。

clone

clone 操作会将变量的值复制一份,这样原来的变量和新的变量就都可以使用了。

这里需要注意的是,clone 操作只会发生在堆上的值,因为在栈上的值是可复制的,所以只能通过 move 操作来转移所有权。

example:

fn main(){
    let s1 = String::from("hello");
    let s2 = s1.clone();
    print!("s1 = {}, s2 = {}", s1, s2);
}

我们对 s1 进行 clone 操作,这样 s1s2 都可以使用了,而且 s1 的所有权也没有被转移,所以后面还可以继续使用 s1

copy

如果一个类型实现了 copy 这个 trait,使用它的变量不会移动,而是被简单地复制,使它们在分配给另一个变量后仍然有效。

example:

fn main() {
    let x = 5;
    let y = x;
    print!("x = {}, y = {}", x, y);
}

x 赋值给 y 后,xy 都可以使用,而且 x 的所有权也没有被转移,所以后面还可以继续使用 x。这是因为 i32 这个类型实现了 copy 这个 trait,所以 x 的值被复制了一份,所以 xy 都可以使用。

以下这些数据类型实现了 copy 这个 trait:

  • 所有的整数类型,例如:u32i32
  • 布尔类型,bool,有 truefalse 两个值。
  • 所有的浮点数类型,例如:f64f32
  • 字符类型,char
  • 元组,当且仅当它们的元素类型都实现了 copy 这个 trait。例如,(i32, i32) 实现了 copy,但是 (i32, String) 就没有实现。

References and Borrowing(引用和借用)

我们将创建引用的动作称为借用。就像在现实生活中一样,如果一个人拥有某样东西,你可以向他们借用。完成后,您必须将其归还。你不拥有它。 引用有以下几个规则:

  • 在任何给定时间,你可以拥有任意数量的引用,但是只能拥有一个可变引用。
  • 引用必须总是有效的。

example1:

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
    s.len()
} // s 作用域失效,但是由于 s 是一个引用,没有所有权,所以不会发生任何事情

上面代码中,我们使用符号 & 来创造一个变量的引用。这里我们使用 &s1 来把这个引用指向 s1。函数 calculate_length 的参数 s 的类型是 &String,这意味着它是一个指向 String 类型的引用,然后在函数体内获取 s 的长度并返回给调用者。

example2:

fn main(){
    // 同一时间可以拥有多个不可变引用
    let s1 = String::from("hello");
    let s2 = &s1;
    let s3 = &s1;
    println!("s1 = {}, s2 = {}, s3 = {}", s1, s2, s3);
}

Mutable References(可变引用)

可变引用指的是可以改变引用值的引用。在同一作用域中,同一时间只能有一个可变引用。

example:

fn main(){
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);
}
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

上面代码中,我们用 mut 先创建了一个可变变量 s,然后使用 &mut s 创建了一个指向 s 的可变引用。函数 change 的入参也是一个指向 String 类型的可变引用,这样我们就可以在函数 change 中改变 s 的值了。

example2:

fn main() {
    let mut s = String::from("hello");
    let r1 = &mut s;
    let r2 = &mut s;  // 在这里。编译器会报错,因为在同一作用域中,同一时间只能有一个可变引用。
    println!("{}, {}", r1, r2);
}
  --> src/main.rs:41:14
   |
40 |     let r1 = &mut s;
   |              ------ first mutable borrow occurs here
41 |     let r2 = &mut s;
   |              ^^^^^^ second mutable borrow occurs here
42 |
43 |     println!("{}, {}", r1, r2);
   |                        -- first borrow later used here

Dangling References(悬垂引用)

悬垂引用是指引用一个不存在的值。在 Rust 中,这是不可能的,因为编译器会在编译时就检查这种情况。下面是一个例子:

fn main() {
    let reference_to_nothing = dangle(); // 获得一个指向不存在值的引用
}
fn dangle() -> &String {
    let s = String::from("hello"); // s 进入作用域
    &s // 返回 s 的引用
} // s 作用域结束,s 被丢弃,内存被释放
  --> src/main.rs:51:16
   |
51 | fn dangle() -> &String {
   |                ^ expected named lifetime parameter

因为变量 s 的作用域只在 dangle 函数内,当 dangle 函数返回 s 的引用时,s 已经被释放了,所以这个引用就是悬垂引用了。 解决这个的方法是返回一个 String 而不是一个引用,这样 s 就不会被释放,而是把 s 的所有权转移给了调用者,也就不存在悬垂引用了。

fn dangle() -> String {
    let s = String::from("hello");
    s
}

以上就是Rust 所有权机制原理深入剖析的详细内容,更多关于Rust 所有权机制的资料请关注我们其它相关文章!

(0)

相关推荐

  • Rust Atomics and Locks内存序Memory Ordering详解

    目录 Rust内存序 重排序和优化 happens-before Relexed Ordering Release 和 Acquire Ordering SeqCst Ordering Rust内存序 Memory Ordering规定了多线程环境下对共享内存进行操作时的可见性和顺序性,防止了不正确的重排序(Reordering). 重排序和优化 重排序是指编译器或CPU在不改变程序语义的前提下,改变指令的执行顺序.在单线程环境下,重排序可能会带来性能提升,但在多线程环境下,重排序可能会破坏程序

  • Rust中的不安全代码详解

    目录 1. 什么是不安全代码 1.1 不安全代码的定义 1.2 不安全代码的作用 2. 如何在Rust中使用不安全代码 2.1 使用unsafe关键字 2.2 不安全代码块的语法 3. 不安全函数和方法 3.1 定义不安全函数和方法 3.2 调用不安全函数和方法 4. 不安全特征和实现 4.1 定义不安全特征 4.2 实现不安全特征 5. 使用不安全代码的风险和注意事项 5.1 不安全代码可能带来的风险 5.2 使用不安全代码时应注意的事项 1. 什么是不安全代码 Rust语言以其出色的内存安全

  • 向Rust学习Go考虑简单字符串插值特性示例解析

    目录 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 简单字符串插值 其他语言例子 Swift Kotlin C Rust 争论矛盾点 总结 fmt.Printf 或 fmt.Sprintf 写拼装字符串业务 在日常开发 Go 工程中,我们经常会用 fmt.Printf 或 fmt.Sprintf 去写类似的拼装字符串的业务. 如下代码: fmt.Printf("Hello Gopher %s, you are %d years old and you're favorite

  • Rust Atomics and Locks并发基础理解

    目录 Rust 中的线程 线程作用域 所有权共享 借用和数据竞争 内部可变 rust 中的线程安全 Send 和 Sync 线程阻塞和唤醒 Rust 中的线程 在 Rust 中,线程是轻量级的执行单元,可以并行执行多个任务.Rust 中的线程由标准库提供的 std::thread 模块支持,使用线程需要在程序中引入该模块.可以使用 std::thread::spawn() 函数创建一个新线程,该函数需要传递一个闭包作为线程的执行体.闭包中的代码将在新线程中执行,从而实现了并发执行.例如: use

  • Rust Atomics and Locks 源码解读

    目录 正文 load 和 store 使用 AtomicBool实现通知线程停止的案例 正文 在 Rust 中,原子性操作是指在多线程并发环境下对共享数据进行操作时,保证操作的原子性,即不会出现数据竞争等问题.Rust 提供了原子类型和原子操作来支持多线程并发编程. Rust 的原子类型包括 AtomicBool.AtomicIsize.AtomicUsize.AtomicPtr 等.这些类型的实现都使用了底层的原子操作指令,保证了它们的读写操作是原子的,不会被其他线程中断. 在 Rust 中,

  • 从迷你todo 命令行入门Rust示例详解

    目录 一个迷你 todo 应用 需要安装的依赖 文件目录组织 主文件 读取文件 状态处理工厂函数 Trait(特征) Create trait Get trait Delete trait Edit trait 导出 trait 为 struct 实现 trait Pending Done 导出 struct Process 输入处理 最后 一个迷你 todo 应用 该文章将使用 Rust 从零去做一个入门级别的 TODO 命令行应用 你将学会什么? 基本的命令行操作 文件读写和文件结构组织 我

  • Rust语言从入门到精通系列之Iterator迭代器深入详解

    目录 迭代器的基本概念 迭代器是什么? Iterator trait Animal示例 迭代器的常见用法 map方法 filter方法 enumerate方法 flat_map方法 zip方法 fold方法 结论 在Rust语言中,迭代器(Iterator)是一种极为重要的数据类型,它们用于遍历集合中的元素.Rust中的大多数集合类型都可转换为一个迭代器,使它们可以进行遍历,这包括数组.向量.哈希表等. 使用迭代器可以让代码更加简洁优雅,并且可以支持一些强大的操作,例如过滤.映射和折叠等. 在本

  • PHP反射机制原理与用法详解

    本文实例讲述了PHP反射机制原理与用法.分享给大家供大家参考,具体如下: 反射 面向对象编程中对象被赋予了自省的能力,而这个自省的过程就是反射. 反射,直观理解就是根据到达地找到出发地和来源.比如,一个光秃秃的对象,我们可以仅仅通过这个对象就能知道它所属的类.拥有哪些方法. 反射是指在PHP运行状态中,扩展分析PHP程序,导出或提出关于类.方法.属性.参数等的详细信息,包括注释.这种动态获取信息以及动态调用对象方法的功能称为反射API. 如何使用反射API <?php class person{

  • JAVA中实现原生的 socket 通信机制原理

    本文介绍了JAVA中实现原生的 socket 通信机制原理,分享给大家,具体如下: 当前环境 jdk == 1.8 知识点 socket 的连接处理 IO 输入.输出流的处理 请求数据格式处理 请求模型优化 场景 今天,和大家聊一下 JAVA 中的 socket 通信问题.这里采用最简单的一请求一响应模型为例,假设我们现在需要向 baidu 站点进行通信.我们用 JAVA 原生的 socket 该如何实现. 建立 socket 连接 首先,我们需要建立 socket 连接(核心代码) impor

  • PHP面向对象自动加载机制原理与用法分析

    本文实例讲述了PHP面向对象自动加载机制原理与用法.分享给大家供大家参考,具体如下: 在学习PHP的面向对象的时候,会知道很多"语法糖",也就是魔术方法.有一个加自动加载的魔术方法,叫:__autoload(); 先看一段代码 <?php function __autoload($classname) { $filename = "./". $classname .".php"; include_once($filename); } new

  • Android6.0 消息机制原理解析

    消息都是存放在一个消息队列中去,而消息循环线程就是围绕这个消息队列进入一个无限循环的,直到线程退出.如果队列中有消息,消息循环线程就会把它取出来,并分发给相应的Handler进行处理:如果队列中没有消息,消息循环线程就会进入空闲等待状态,等待下一个消息的到来.在编写Android应用程序时,当程序执行的任务比较繁重时,为了不阻塞UI主线程而导致ANR的发生,我们通常的做法的创建一个子线程来完成特定的任务.在创建子线程时,有两种选择,一种通过创建Thread对象来创建一个无消息循环的子线程:还有一

  • C++ Assert()断言机制原理以及使用方法

    MSDN原文如是说: Evaluates an expression and, when the result is false, prints a diagnostic message and aborts the program. (判断一个表达式,如果结果为假,输出诊断消息并中止程序.) void assert( int expression ); 参数:Expression (including pointers) that evaluates to nonzero or 0.(表达式[

  • Java 反射机制原理与用法详解

    本文实例讲述了Java 反射机制原理与用法.分享给大家供大家参考,具体如下: 反射反射,程序员的快乐! 1.什么是反射? Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:并且能改变它的属性.而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言.从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是

  • PHP的Trait机制原理与用法分析

    本文实例讲述了PHP的Trait机制原理与用法.分享给大家供大家参考,具体如下: Trait介绍: 1.自PHP5.4起,PHP实现了一种代码复用的方法,称为trait. 2.Trait是为类似PHP的单继承语言二准备的一种代码复用机制. 3.Trait为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用method. 4.trait实现了代码的复用,突破了单继承的限制: 5.trait是类,但是不能实例化. 6.当类中方法重名时,优先级,当前类>trait>父类; 7

  • Python代码块及缓存机制原理详解

    这篇文章主要介绍了Python代码块及缓存机制原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.相同的字符串在Python中地址相同 s1 = 'panda' s2 = 'panda' print(s1 == s2) #True print(id(s1) == id (s2)) #True 2.代码块: 所有的代码都需要依赖代码块执行. ​ 一个模块,一个函数,一个类,一个文件等都是一个代码块 ​ 交互式命令中, 一行就是一个代码块

  • 简单了解java等待唤醒机制原理及使用

    这篇文章主要介绍了简单了解java等待唤醒机制原理及使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 这是一篇走心的填坑笔记,自学Java的几年总是在不断学习新的技术,一路走来发现自己踩坑无数,而填上的坑却屈指可数.突然发现,有时候真的不是几年工作经验的问题,有些东西即使工作十年,没有用心去学习过也不过是一个10年大坑罢了(真实感受). 刚开始接触多线程时,就知道有等待/唤醒这个东西,写过一个demo就再也没有看过了,至于它到底是个什么东西,

  • Java方法参数传递机制原理解析

    这篇文章主要介绍了Java方法参数传递机制原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Java方法中如果声明了形参,在调用方法时就必须给这些形参指定参数值,实际传进去的这个值就叫做实参. 这就涉及到Java中的参数传递机制,值传递. 基本数据类型 基本数据类型,值传递的体现是数值的传递. public class TransferTempTest { public static void main(String[] args) {

随机推荐