Rust入门-所有权

2024-04-20 09:44
文章标签 rust 入门 所有权

本文主要是介绍Rust入门-所有权,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、为什么、是什么、怎么用

1、为什么Rust要提出一个所有权和借用的概念

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成为所有编程语言设计的难点之一。

主要分为三种流派:

  1. (1)垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,典型代表:Java、Go

    (2)手动管理内存的分配和释放, 在程序中,通过函数调用的方式来申请和释放内存,典型代表:C++

    (3)通过所有权来管理内存,编译器在编译时会根据一系列规则进行检查避免手动或者运行时垃圾回收带来的额外成本

讲到内存,在编程语言中,都会讲到栈、堆,栈和堆的结构特性决定了一些值适合放在哪些位置,能够有更好的性能和空间效率。

  1. (1)栈,主要是存储局部变量,栈中的所有数据都必须占用已知且固定大小的内存空间。

    优点:用完即出,也很好出,性能很好 缺点:无法存储大小未知或者可能变化的数据。

    (2)堆,对于大小未知或者可能变化的数据,我们需要将它存储在堆上。

    优点:空间大,能够存储大小未知或者可能变化的数据。 缺点:数据组织较为复杂,需要回收内存空间。

2、Rust所有权是什么

先来介绍所有权的几个概念

  1. Rust 中每一个值被一个变量所拥有,该变量被称为值的所有者,且有且仅有一个所有者

    所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

特别地,为了便于理解,我认为基本数据类型值**,比如i32、boolean等基本类型,有别的机制处理这种基本类型的所有权问题:会直接拷贝栈上数据,新生成一个值,把新值的所有权给新变量,不会把旧值的所有权给新变量(新拥有者),没有发生所有权变化的现象。这种叫做Copy行为。

先说一下字符串类型,注意我说的是字符串类型,对于

let s = "hello";

上面这段代码中的“hello”,可以理解成Java中的字符串字面值不是存储在堆上的,可以想象成存储在一个文件里的。和字符串类型不是一个东西,对于这种值,可以理解成没有所有权的概念,大家都只是持有一个引用它的指针。

那么 什么是字符串类型的数据,比如

let s1 = String::from("hello");
let s2 = s1;

我们来分析上述代码的

第一行代码 String::from(“hello”);

  1. 会在堆中找到一片地址空间,存储字符串类型数据"hello"。并返回该堆中数据的地址、长度、容量等数据,此时堆中的数据就叫做值

    然后在栈中生成一个结构体变量s1,该结构体就是字符串结构,保存了堆中数据的地址、长度、容量等数据。s1变量堆中数据(值)的拥有者

第二行代码 let s2 = s1;

这种行为,就是将s1的值的所有权移交给了s2,即堆中数据"hello"此时的拥有者是变量上s2s1已经没有"hello"的所有权,我们不能再通过s1访问或者修改堆中数据"hello"。

如果此时想通过s1再次访问堆中数据"hello",就会报错


fn main() {let s1 = String::from("hello");let s2 = s1;println!("{}, world!", s1);}
error[E0382]: borrow of moved value: `s1`--> src/main.rs:6:26|
3 |   let s1 = String::from("hello");|       -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
4 |   let s2 = s1;|            -- value moved here
5 |   
6 |   println!("{}, world!", s1);|                          ^^ value borrowed here after move|= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable

再来说一下这样移交所有权有什么好处。

  1. 当变量离开作用域后,Rust 会自动调用 drop 函数并清理变量的堆内存。不过由于两个 String
    变量指向了同一位置。这就有了一个问题:当 s1 和 s2 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free) 的错误,也是之前提到过的内存安全性 BUG 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞

可以理解成,所有权移交之后,就可以大胆放心的抛弃先前的拥有者。来个图加深一下印象
请添加图片描述

s1不再指向堆中数据,s2指向堆中数据

3、我就不想移交所有权,我又想生成一个新变量指向相同数据

这里可以用到深拷贝,即在堆中生成一份相同的数据,赋给新变量。如代码


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

但是这么做是有性能消耗的,因为你需要复制一份数据,万一你这个数据非常大,复制起来非常耗时耗资源

而对于栈上变量,直接都是深拷贝,其实不是叫深拷贝,是达到深拷贝的效果,但是Rust叫做Copy

这里可以给出一个通用的规则: 任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy 的。如下是一些 Copy 的类型:

  1. 所有整数类型,比如 u32
  2. 布尔类型,bool,它的值是 true 和 false
  3. 所有浮点数类型,比如 f64 字符类型,char
  4. 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是
  5. 不可变引用 &T ,

可变引用 &mut T 是不可以 Copy的

3、函数传参和返回值,都是会移交所有权的

fn main() {let s = String::from("hello");  // s 进入作用域takes_ownership(s);             // s 的值移动到函数里 ...// ... 所以到这里不再有效let x = 5;                      // x 进入作用域makes_copy(x);                  // x 应该移动函数里,// 但 i32 是 Copy 的,所以在后面可继续使用 x} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,// 所以不会有特殊操作fn takes_ownership(some_string: String) { // some_string 进入作用域println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

你可以尝试在 takes_ownership 之后,再使用 s,看看如何报错?例如添加一行 println!(“在move进函数后继续使用s: {}”,s);。

有时我就想用一下,老是移来移去多麻烦,下篇我们讲引用与借用

这篇关于Rust入门-所有权的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/919913

相关文章

Rust 数据类型详解

《Rust数据类型详解》本文介绍了Rust编程语言中的标量类型和复合类型,标量类型包括整数、浮点数、布尔和字符,而复合类型则包括元组和数组,标量类型用于表示单个值,具有不同的表示和范围,本文介绍的非... 目录一、标量类型(Scalar Types)1. 整数类型(Integer Types)1.1 整数字

Rust中的Option枚举快速入门教程

《Rust中的Option枚举快速入门教程》Rust中的Option枚举用于表示可能不存在的值,提供了多种方法来处理这些值,避免了空指针异常,文章介绍了Option的定义、常见方法、使用场景以及注意事... 目录引言Option介绍Option的常见方法Option使用场景场景一:函数返回可能不存在的值场景

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联

poj 2104 and hdu 2665 划分树模板入门题

题意: 给一个数组n(1e5)个数,给一个范围(fr, to, k),求这个范围中第k大的数。 解析: 划分树入门。 bing神的模板。 坑爹的地方是把-l 看成了-1........ 一直re。 代码: poj 2104: #include <iostream>#include <cstdio>#include <cstdlib>#include <al

MySQL-CRUD入门1

文章目录 认识配置文件client节点mysql节点mysqld节点 数据的添加(Create)添加一行数据添加多行数据两种添加数据的效率对比 数据的查询(Retrieve)全列查询指定列查询查询中带有表达式关于字面量关于as重命名 临时表引入distinct去重order by 排序关于NULL 认识配置文件 在我们的MySQL服务安装好了之后, 会有一个配置文件, 也就

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非