文档 https://doc.rust-lang.org/stable/book/ch04-01-what-is-ownership.html
所有权(ownership)
所有权是Rust的一个核心概念,是一系列Rust用于内存管理的规则。
在Rust中,数据的存储的地方,主要有堆Heap和栈Stack
常规的数据(简单理解为长度已知且固定的数据)存储在栈中,一些复杂的数据结构(简单理解为长度但为固定的可变数据)则存储在堆中。
所有权规则 Ownership Rules
在Rust中,所有权的规则,主要有以下三条。
- 每一个值都会有一个owner
- 每一个值在某一时刻有且只有一个owner
- 当owner离开作用域后,值会被销毁
变量作用域 Variable Scope
变量作用域,简单理解为变量的有效范围,比如在写代码时,需要在不同地方定义不同的变量,不同的变量会有其不同的作用范围,rust的变量作用域,大抵如此。
最常见的,便是用花括号定义一个作用域。
fn main() { // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
}
简言之
- 当变量(owner )走入作用域定,它变得有效了
- 当变量(owner )走出作用域后,它变得无效了
Rust是怎么销毁无效变量(owner)呢?
当一个变量(owner)变得无效后,rust会帮忙调用drop()方法,来销毁(owner)。当然在代码中也可以自己收到销毁,只是没太大必要。
变量和数据的移动交互
当变量被赋值给另外一个变量时,会导致所有者的改变。
试想一下,如果一个值,被赋值给一个变量后,这个变量又被赋值给另一个变量,会发生什么呢?
数值
fn main() {
let x = 5;
let y = x;
println!("x: {}, y: {}", x, y);
}
Compiling variables v0.1.0 (D:\learning\rust\variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.35s
Running `target\debug\variables.exe`
x: 5, y: 5
常规字符串
fn main() {
let x = "5";
let y = x;
println!("x: {}, y: {}", x, y);
}
Compiling variables v0.1.0 (D:\learning\rust\variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
Running `target\debug\variables.exe`
x: 5, y: 5
可变字符串
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("s1: {}, s2: {}", s1, s2);
}
Compiling variables v0.1.0 (D:\learning\rust\variables)
error[E0382]: borrow of moved value: `s1`
--> src\main.rs:4:32
|
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 | println!("s1: {}, s2: {}", s1, s2);
| ^^ 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
|
3 | let s2 = s1.clone();
| ++++++++
For more information about this error, try `rustc --explain E0382`.
error: could not compile `variables` (bin "variables") due to previous error
* The terminal process "C:\Users\Leon\.cargo\bin\cargo.exe 'run', '--package', 'variables', '--bin', 'variables'" terminated with exit code: 101.
以上的三个例子中,当s2被赋值为String::from(“hello”),会导致编译错位,为什么呢?
这时因为rust在管理内存时,不同的数据会被分别存放于堆或者栈中,赋值操作仅发生在栈中。
数据存储方式
对于已知长度为固定的值,rust把值存在栈中;
而对于长度未知,不固定的值,rust则把数据存在堆中,并且在栈中存储一份指向堆中数据的指针。
变量赋值时所有权的变化
当变量被复值时,会在栈中生成一个值,如果变量被赋值给另一个变量, 则生产变量的值的一份copy,并指向新的变量。
比如数值的赋值例子
let x = 5;
let y = x;
在栈中,把5赋值给x,当执行y=x时,则生产5的一份复制的值,并赋值给y。
同理,对于以下代码,也是一样的。
let x = "5";
let y = x;
所以,x,y分别为两个值(虽然相同)的所有者。
然后,对于以下代码,则稍微不同。
let s1 = String::from("hello");
let s2 = s1;
当String::from(“hello”)被赋值给x时,内存中的情况时这样的。
- 在堆中生成一个字符串,值为“hello”
- 在栈中生成一个变量x,并指向堆中的“hello”
- 当y = x,在堆中复制一份的x,那么,x,y都会成为"hello"的所有者,会跟所有者的规则冲突,因此产生编译错误。
具体如下图所示
要这么解决这个问题呢?把堆中的数据复制一份即可
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1: {}, s2: {}", s1, s2);
}
[Running] cd "d:\learning\rust\variables\src\" && rustc main.rs && "d:\learning\rust\variables\src\"main
s1: hello, s2: hello
如果写过Java代码的应该知道,当变量指向一个类时,如果把变量赋值给另一个变量,一旦数据发生改变,两个变量指向的数据都会方式改变。
特别是把此类变量传给不同函数时,很容易不小心把元数据进行改动,导致后面要花好多时间去检查。
public class Util {
public static void main(String[] args) {
List<String> path = new ArrayList<>();
List<String> path1 = path;
System.out.println("path = " + path);
System.out.println("path1 = " + path1);
path1.add(" allen");
System.out.println("path = " + path);
System.out.println("path1 = " + path1);
}
}
C:\sandbox\openjdk-20.0.2_windows-x64_bin\jdk-20.0.2\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2\lib\idea_rt.jar=60780:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2\bin" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath C:\Users\Leon\IdeaProjects\java-demo\target\classes;C:\Users\Leon\.m2\repository\com\google\code\gson\gson\2.8.9\gson-2.8.9.jar file.Util
path = []
path1 = []
path = [ allen]
path1 = [ allen]
或许,这是rust为了在一定程度上减少数据被随意修改,造成不可测的异常而做的吧。
总结
- ownership时rust用于管理内存的方式
- 变量只有在自己的作用域内有效,超出作用域后,变量被销毁
- 长度固定的不可变数据,如数值,字符串,变量和值都存储在栈中,变量可以随意被赋值给别的变量,并生成原值的复制给新的变量,原变量的所有者不变。
- 长度不固定的可变数据,如数值,字符串数据结构,变量存储在栈中,而实际的值则存储在堆中。简单地把变量赋值给另外一个变量,指挥在栈中复制一份同样指向元数据的指针,导致堆中的一份数据有了多个所有着,产生冲突。解决办法为复制一份堆中的数据,如使用clone方法