07_rust的所有权
所有权
所有权是rust最独特、核心的特性,使得rust无GC也可保证内存安全。
其他语言都有在运行时管理自身内存的机制,比如GC,或者程序员手动申请和释放。
rust则采用了第三种方式:所有权
- 内存通过一所有权系统来管理,含一组编译时用于检测的规则。
- 当程序运行时,所有权不会影响程序速度。
stack(栈)和heap(堆)内存
stack按值的接收顺序来存储,按相反的顺序移除(先进先出,LIFO),分别是压栈和弹栈。所有存入stack的数据必须已知固定大小。
heap用于存储编译时大小未知运行时才确定大小、及可能发生变化的数据,需代码中手动申请,然后系统找到一块适合大小的空间并返回指针(这个过程叫分配内存),然后存入数据。
所有权解决的问题:
- 跟踪代码哪些地方在使用heap的哪些数据
- 清理heap上未使用的数据,避免空间不足
- 最小化heap上的重复数据
所有权规则
- 每个值都有一个变量,每个变量是该值的所有者
- 每个值同时只能有一个所有者
- 当所有者超出作用域(scope)时,该值将被删除
以string类型为例了解所有权
String类型定义和试用
fn main() { let mut s = String::from("Hello"); // 在运行时申请分配相应大小的内存 s.push_str(" world"); println("{}", s); }
rust采用的回收方式:对于某个值,当拥有它的变量离开作用范围是,内存会立即自动交还给操作系统。会自动调用drop函数。
变量和数据的交互方式:移动(Move)
多个变量可与同一个数据使用move方式来交互,如
fn main() { let s1 = String::from("Hello"); let s2 = s1; }
一个String由3部分组成:一个指向存放字符串内容的内存指针,一个长度,一个容量。
比如s1
string s1: # 以下三个字段构成一个String对象,存放在stack上 ptr: 0x1234 # 内存首地址 len: 5 # 存放字符串内容所需的字节数 capacity: 5 # 存放string从操作系统里获得的总字节数 # 以下是heap上开辟的内存 0x1234: hello
而执行let s2 = s1时,只会复制一份stack的数据,ptr的值不变,指向heap里的同一个地址。
s1 ==> s2 string s2: ptr: 0x1234 # 也指向heap上的hello len: 5 capacity: 5
当离开作用域时,rust会自动调用drop函数,并将变量使用的heap内存释放,当s1、s2离开作用域时,都会尝试释放相同的内存。
在c中,这种现象称之为二次释放(double free)错误。
在rust中为了保证内存安全,执行let s2 = s1后,rust会让s1失效,这样在离开s1作用域时不需再释放ptr指向的内存。
如果还继续使用s1则会编译报错:
fn main() { let s1 = String::from("Hello"); let s2 = s1; println!("test {}", s1); } /* 编译报错 25 | let s1 = String::from("Hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 26 | let s2 = s1; | -- value moved here 27 | println!("test {}", s1); | ^^ value borrowed here after move */
之前有浅拷贝(shallow copy)和深拷贝(deep copy)的概念,
而rust做的一种移动操作,相当于让s2替代了s1,s1失效。
隐含的一个设计原则:rust不会自动创建数据的深拷贝。
如果想深拷贝,实现heap数据拷贝,则使用克隆(Clone)方法。
fn main() { let s1 = String::from("Hello"); let s2 = s1.clone(); println!("test {}", s1); }
Copy trait
stack上的数据:复制,无需考虑深拷贝、浅拷贝的问题,也就没有移动失效的问题。
fn main() { let x = 5; let y = x; println!("test {} {}", x, y); }
Copy trait:trait可简单理解为接口,可用于像整数这些完全存放在stack上的数据类型,如果一个数据类型实现了Copy trait,则旧的变量在赋值后仍然可用。
如果一个类型或者该类型的一部分实现了Drop trait,则Rust不允许它再实现Copy trait。
一些拥有Copy trait的类型
- 任何简单标量的组合类型都可Copy
- 任何需要分配内存或某种资源的都不可Copy
一些拥有Copy trait的类型:
- 所有的整数类型,如u32
- bool
- char
- 所有浮点类型,如f64
- Tuple(元组,如果其所有字段都是Copy的),如(i32, i32)是,(i32, String)则不是
所有权和函数
-
参数
在语义上,将值传递给函数和把值赋给变量是类似的,将值传递给函数将发生移动或复制。
如果参数类型是String等类型,则Move操作,如果是i32类型,则Copy操作。 -
返回值
在返回值的过程中也同样会发生所有权的转移。
一个变量的所有权总是遵循同样的模式:
- 赋值过程发生Move/Copy。
- 当包含heap数据的变量离开作用域时,值会被drop函数清理,除非数据的所有权移动到了另一个变量上。
如果想保留所有权,则可使用引用的方式。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!