Rust所有权
所有权使得Rust无需垃圾回收器(garbage collector
)即可保证内存安全。
Rust中的栈(Stack)和堆(Heap)
- 栈:以放入值顺序存储值,以相反的方向取出值。(后进先出),栈中所有数据必须占用已知且固定的大小,在编译未知大小或者大小可以变化的数据,要存储在堆上。
- 栈中增加数据叫做进栈,移出数据叫做出栈。
- 堆:堆中存储数据,要先请求一定大小的空间,并标记为已经使用,返回一个表示该地址位置的指针。(这个过程叫做在堆上分配内存)
- 指针可以存储在栈上,但实际需要时,必须访问指针。指针大小是固定的,所以不会定义为==“分配”==
- 入栈相比在堆上分配内存较快,因为入栈无需分配新的内存,内存会存储在栈顶,而堆上分配内存会先找到足够存放数据的内存空间,然后为分配内存做准备。
- 访问堆上的内存需要使用指针访问,所以访问数据时栈相比堆也较快,在堆上分配大量的空间会消耗时间,数据距离较近(栈)相比较远(堆)中能更好的工作。
- 当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。
而所有权的出现就时为了更加有效的管理堆中的数据。
所有权规则
变量的作用域
fn main{
let s = "hello";
}
- 这个变量在main函数内部都有效,这个就是它的作用域。
String 类型
- string是可变的,而字面量是不可变的。
fn main() {
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() 在字符串后追加字面值
println!("{}", s); // 将打印 `hello, world!`
}
- 字符串字面量是通过硬编码存储在程序中,但是他们是不可变的。
- 可以使用
from
函数来进行基于字符串字面变量来创建String
,同时可以使用s.push_str("xxxx")
在字符串之后添加新的字符串字面值。
内存和分配
下面是官方文档的描述:
fn main() {
{
let s = String::from("hello"); // 从此处起,s 开始有效
// 使用 s
} // 此作用域已结束,
// s 不再有效
}
Rust中释放内存代码自动使用drop函数,使用位置在"}"
处使用。在C++中又称做资源获取即初始化
变量和数据交互的方法(一):移动
let x = 5;
let y = x;
- 多个变量可以和一个数据以不同的方式交互。
let s1 = String::from("hello");
let s2 = s1;
但是string和整形变量的交互方式并不相同,string组成部分有三个,指向存放字符串内容内存的指针,一个长度,一个容量。
下面是官方文档:
简而言之,Rust中在拷贝过程中只拷贝了指针,长度,容量,而没有直接拷贝堆上数据。
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
}
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value borrowed here after move
For more information about this error, try `rustc --explain E0382`.
error: Could not compile `ownership` due to prevIoUs error
Rust禁止无效引用。
在其它语言中,将指针、长度、容量进行拷贝而不对数据拷贝叫做浅拷贝,对应的有深拷贝,而在Rust中不仅做出了浅拷贝,同时使第一个变量无效,这种方式叫做移动。
这种方式,有点类似指针指向同一个地址。
但是s1此时是无效的。
变量和数据交互方式(二):克隆
如果需要深拷贝,需要使用一个函数clone
,但是这会导致运行速度降低。
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
}
这时,处在堆上的数据是被复制的。复制方式如图4.3.(官方文档)
只在栈上的数据:拷贝
与String不同,整型变量为大小已知的变量,存储在栈上,下面代码中:
fn main() {
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
}
- 会正确的编译出来,也就是说,此时x、y都是有效的变量。
- Rust中变量有
copy trait、Drop trait
等trait
、并且这两种trait
是不能够相互兼容的,类似string
的无法使用copy trait
。 - 作为通用的标准,任何一组简单标量值组合都能够实现copy,任何不需要分配内存或某种形式资源类型可以实现copy,下面是常见的copy类型:
所有权和函数
- 示例使用注释展示变量何时进入和离开作用域
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 移出作用域。不会有特殊操作
下面是错误代码,在some_string结束时会使用drop释放内存。
返回值和作用域
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 移出作用域并被丢弃。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 并移出给调用的函数
}
-
这里s1得到了
give_ownership()
函数中的返回值 -
s3得到了
take_and_gives_back()
函数中的返回值。 -
此时需要注意的是:s2已经被移动到takes_and_gives_back中,后面再使用时就使用不了了。
若在主函数末尾添加println!("{}",s2);
会报错,显示borrow of moved value:
s2。
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)
}