所有权
所有权
对Rust 所有权转移的理解
1、每一个值都只有一个变量所拥有,这个变量被称为值的所有者
2、一个值只能同时被一个变量所拥有
3、当所有者离开作用域范围时,这个值被销毁
fn main(){
let a = String::from("hello"); // a 是这个字符串的所有者
let b = a; // a的所有权转移到了b上,a被销毁
// println!("{}",a); // 编译报错 a 已经被销毁
println!("{}",b);
} // 函数执行完成后 b 离开作用域范围,b被销毁程序的传参数,和赋值会发生所有权的转移,具体发生哪个过程取决于传递的哪个参数它数据类型是什么,如果是实现了copy trait 的数据类型就会发生复制的过程,如果没有实现copy trait 的类型就会发生所有权转移
fn main(){
let name = String::from("张三");
let age = 18;
let man = new_man(name, 18);
// println!("{:?}",name); // 报错,因为String没有实现copy trait,name的所有权转移到了new_man的name参数上
println!("{:?}",age); // 编译通过 age实现了copy trait,所以age的值被复制到了new_man的age参数上
}
fn new_man(name: String, age: i32) {
...
}函数的返回对应没有实现 copy trait 的类型会把所有权转移到接受这个函数返回的变量上
fn new_man(name: String) {
return Man{name}
}
fn main(){
let name = String::from("张三");
let man = new_man(name); // 这里先将 String 的所有权转移到 new_man 的name 参数上,然后 new_man 函数执行完成后将 Man 的所有权返回到 man 变量上
println!("{:?}",man.name)
}引用和借用
引用是一种指针(用来保持一个内存地址的变量),指向某个值的地址,允许你访问该值但不拥有所有权。
借用是一种行为,表示通过引用临时访问数据的所有权。当你使用引用时,实际上是在“借用”数据的所有权,而不是转移它。
获取一个变量的引用,被称为借用
let x = 5 // x -> 0x0001 -> 5
let y = &x; // y -> 0x0002 -> 0x0001
// y 是 x 的引用,实际上 y 保存的是 x 在内存中的地址
println!("y: {:p}",y);这里我们假设x和y的内存地址,x实际上是0x0001这个地址的别名(更专业的名称是: 符号Symbol),y上保存的数据实际上是 x 的内存地址,我们在使用时rust会自动帮我们解引用,看上去y上的值就是5,实际上并非如此
let x = 5;
let mut y = &x;
y = 6 // 这里会报错,因为y是一个引用,它的类型是指针,而我们在这里给了它一个int类型的值,所以会报错
y = &6 // 如果我们给它一个指针,那么就不会报错了这里我们可以理解为 &x 就是 x 在内存上的地址
let x = 5;
let y = &x; // y 是 x 的引用
println!("{:p}",*y) // 这里会打印一个地址,这个地址就是 x 在内存上的地址
println!("{:?}",*y); // 我们可以手动解引用,打印5
println!("{:?}",y); // 实际上int类型的指针实现了 Deref trait,所以我们也可以直接打印y,而不需要手动解引用,这里也会打印5如果我们想获取一个地址上的值,我们需要使用解引用操作符 *, 智能指针类型在传递的时候 因为实现了 Deref trait 所以不用我们手动去解引用,Rust会自动帮我们完成
我们通过这段代码的汇编表现形式来理解rust的引用是如何操作内存的
let mut x = 5;
let y = &mut x;
let z = *y;
*y = 10;
println!("y: {}, z: {}", y, z); // 这里会打印 y: 10, z: 5使用 RUSTFLAGS="--emit asm -C llvm-args=-x86-asm-syntax=intel" cargo build --release 编译这段代码,得到的汇编代码如下,这里我们省略了一些无关的代码
add x8, sp, #12 // x8 <= p*[sp+12] | 程序首先计算sp+12在内存上的地址给到了寄存器x8上,这里sp+12上还没有数据;
str x8, [sp, #16] // sp+16 = x8 | 然后x8寄存器上的值给到了sp+16,而x8上现在保存着之前sp+12的内存地址;
mov w8, #5 // w8 <= 5; | 随之程序向寄存器w8上写入了一个数据 5;
str w8, [sp, #28] // sp+28 = w8; | 接下来把寄存器w8上的数据拷贝到了内存sp+28上,这个时候w8 = 5;
mov w8, #10 // w8 <= 10; | 紧接着我们把寄存器w8上的数据修改成了10;
str w8, [sp, #12] // sp+12 = w8; | 最后把寄存器w8上的数据拷贝到了内存sp+12上,这个时候w8 = 10;在这里 sp+12、sp+16 和 sp+28 实际上就是 x、y 和 z 在汇编中的表现形式, 是程序为这些变量分配的内存空间。
上面我们用拷贝这个词来描述了str这个将寄存器中的数据给到内存的行为,str实际上是将寄存器上的值复制一份给到内存,它既不改变也不移动寄存器上的数据,所以我们可以看到let z = *y实际上是把w8 = 5时w8上的值复制一份给到了z上,现在z上的值是5,而不是10,这就是为什么我们在修改*y的值时,z的值不会跟着改变的原因
可变引用和不可变引用
不可变引用和可变引用不能同时存在,因为可变引用会修改数据,不可变引用不能读取被修改后的数据
let mut x = 5;
let y = &x; // 不可变引用
let z = &mut x; // 可变引用,编译报错,因为不可变引用和可变引用不能同时存在