Rust入门-05-所有权
所有权(ownership)
栈内存(Stack)与堆内存(Heap)
- 栈
- 按值的接收顺序来存储,按相反的谁徐将他们移除(后进先出)(last in, first out)
- 添加数据叫(压)入栈(pushing onto the stack)
- 移出数据叫(弹)出栈(popping off the stack)
- 储存在栈内的数据必须拥有已知的固定大小
- 指针也有固定大小,也可以存放在栈内
- 堆
- 向堆放入数据时,要请求一定大小的空间,将该空间标记为已使用,并返回一个表示该位置地址的指针。这个过程叫做在堆上分配内存(allocating on the heap),简称为 “分配”(allocating)
- 栈和堆的比较
- 入栈比分配要快
- 新数据一直在栈的顶端,操作系统不需要寻找新的空间来储存新数据
- 访问堆上的数据比访问栈上的数据慢
- 访问堆上的数据时要通过指针访问(间接访问)
所有权规则
- 每一个值都有一个变量,该变量叫做所有者(owner)
- 每个值同时只能有一个所有者
- 当所有者离开作用域时,这个值将被删除
String 类型
-
字符串字面量,是不可变的
-
String是第二种字符串类型- 在堆上分配
创建String类型
-
使用
from函数 基于字符串字面量来创建Stringlet s = String::from("hello");- (
::)表示from是string类型下的函数 - 变量
s是可以修改的
fn main() { let mut s = String::from("hello"); s.push_str(", world!"); // push_str() 在字符串后追加字面值 println!("{}", s); //输出结果是 hello, world! //"{}"是 替换占位符 //占位符就是先占住一个固定的位置,等着你再往里面添加内容的符号 }但字符串字面量是不可以修改的
- (
内存与分配
-
字符串字面值,被直接硬编码进最终的可执行文件中
- 不可变
- 快速且高效
-
String类型,为了其可变性,要在堆上分配未知大小的内存- 操作系统必须在运行时来请求内存
- 这步通过
String::from来实现
- 这步通过
- 使用
String之后,要将其内存返回给操作系统- 在有 垃圾回收(garbage collector,GC)的语言中, GC 记录并清除不再使用的内存
- 但没有GC的语言中,需要我们去识别不再使用的内存并调用代码将其返回(显式释放
- 忘记回收,会浪费内存
- 过早回收,会出现无效变量(变量非法
- 重复回收,会出现bug
- 操作系统必须在运行时来请求内存
-
Rust的内存分配:对于某个值来说,当拥有其的变量离开作用域后就会被自动释放
{ let s = String::from("hello"); // 从此处起,s 开始有效 // 使用 s } // 此作用域已结束, // s 不再有效- 当变量
s离开作用域时,Rust会自动调用drop函数
- 当变量
变量与数据交互的方式(一):移动(Move)
多个变量能够以不同的方式与同一数据交互。
-
普通例子
let x = 5; let y = x;- 变量
x与y都与整数5发生了交互 - 整数是有已知固定大小的简单值,所以这两个
5被放入了栈中
- 变量
-
String版本let s1 = String::from("hello"); let s2 = s1; //即 let s2 <- s1;-
将值
"hello"绑定给s1的String在内存中的表现形式-
String由三部分组成(这一组数据存放在栈上
- 存放字符串内容的内存的指针
- 长度(len存放字符串内容所需的字节数)(即实际存的内存字节数
- 容量(capacity是指String从操作系统总共获得内存的总字节数)(最多能存的内存字节数
-
存放字符串内容的内存部分(也为String的一部分)在堆上
-
(云传)-
当把
s1赋给s2时,String的数据复制了一份- 再栈上复制了一份指针、长度、容量
- 并没有复制指针所指向堆上的数据
(云传) -
当变量离开作用域时,Rust会自动调用
drop函数,将变量使用的堆内存释放当
s1和s2离开作用域时,它们会尝试释放相同的内存- 二次释放(double free)bug(可能会是某些正在使用的数据发生损坏)
-
为了确保内存安全,在
let s2 = s1之后,Rust 认为s1不再有效
let s1 = String::from("hello"); let s2 = s1; //此时 s1 已经失效 // println!("{}, world!", s1); 该代码不能运行
- 相当于将
s1的值移动给了s2- 之后
s2离开作用域时,会释放自己的内存
- 之后
![]()
-
变量与数据交互的方式(二):克隆(Clone)
确实需要复制String中堆上的数据,可以使用clone通用函数的方法(clone方法

-
eg
let s1 = String::from("hello"); let s2 = s1.clone(); //堆上的数据也被复制了 println!("s1 = {}, s2 = {}", s1, s2); //s1没有失效该代码能正常运行
-
对于在栈上的数据:拷贝(Copy)
let x = 5; //x为整数类型(整形),编译时就确定了自己的大小,并储存在栈上 let y = x; //所以拷贝其的值是快速的,也不会使x失效 //此时调用clone函数与浅拷贝没有什么不同 println!("x = {}, y = {}", x, y);该代码能正常运行
-
Copytrait (接口)可以用在类似整型这样的存储在栈上的类型上- 如果一个类型实现了
Copytrait,那么一个旧的变量在将其赋值给其他变量后仍然可用 - 如果一个类型或该类型的一部分实现了
Dorptrait ,那么Rust不允许让其再去实现Copytrait(会报错
- 如果一个类型实现了
-
可以实现
Copytrait 的类型- 任何简单标量的组合类型可以实现
- 任何需要分配内存或某种资源的不可以
- 所有整数类型,比如
u32 - 布尔类型,
bool,它的值是true和false - 所有浮点数类型,比如
f64 - 字符类型,
char - 元组,当其中所包含的字段都可以
Copy时- 比如,
(i32, i32)实现了Copy - 但
(i32, String)就没有
- 比如,
- 所有整数类型,比如
所有权和函数(所有权的转移
-
将值传递给函数在语义上与给变量赋值相似。
- 向函数传递值可能会移动或者复制,就像赋值语句一样。
fn main() { let s = String::from("hello"); // s 进入作用域 takes_ownership(s); // 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 移出作用域。不会有特殊操作即所有权发生了转移,从main函数转移到了子函数,此时由子函数负责释放
返回值与作用域
-
返回值也可以转移所有权
fn main() { let s1 = gives_ownership(); // gives_ownership 将返回值移给 s1 let s2 = String::from("hello"); // s2 进入作用域 let s3 = takes_and_gives_back(s2); // s2 被移动到 takes_and_gives_back 中 // 将返回值移给 s3 } // s3 移出作用域,s3无效 // s2 也移出作用域,但其所有权当作返回值被移动给了s3,所以 s2 不会发生什么事情 // s1 移出作用域并被丢弃 fn gives_ownership() -> String { // gives_ownership 将返回值移动给调用它的函数 let some_string = String::from("yours"); // some_string 进入作用域 some_string // 返回 some_string 并移出给调用的函数 } // takes_and_gives_back 将传入字符串并返回该值 fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域 a_string // 返回 a_string 并移出给调用的函数 }即 s2 进入main函数作用域 —> 移动到 takes_and_gives_back()函数 —> a_string 作为函数返回值 —> 移动给 s3
即 s2 的所有权被移走又移进来
其 s2 的所有权移动到了 s3
所以 s2 离开作用域时,因为其所有权移动到了 s3 ,且 s3 先移出了作用域并使 s3 失效,所以 s2离开时,s2 没有发生事情
-
变量的所有权总是遵循相同的模式:
- 将值赋给另一个变量时移动其所有权
- 当持有堆中数据值的变量离开作用域时,其值将通过
drop被清理掉,除非数据的所有权被移动到另一个变量上
-
函数使用一个值但不获取所有权
- 但这种做法较为麻烦
fn main() { let s1 = String::from("hello"); let (s2, len) = calculate_length(s1); println!("The length of '{}' is {}.", s2, len); } fn calculate_length(s: String) -> (String, usize) { let length = s.len(); // len() 返回字符串的长度 (s, length) }即将 s1 传递为函数参数,再作为函数结果进行了返回,s1 的所有权传递给了 s2
即 calculate_length(s: String) 函数只取得了 s1 的字符长度,没有获得 s1 的所有权(将其所有权交给了 s2

(云传)
浙公网安备 33010602011771号