《Rust权威指南》学习笔记——4. 认识所有权
Rust权威指南学习笔记——认识所有权
什么是所有权
1. 所有权规则
- Rust中的每一个值都有一个对应的变量作为它的所有者。
- 在同一时间内,值有且仅有一个所有者。
- 当所有者离开自己的作用域时,它持有的值就会被释放掉。
2. 变量作用域
- s在进入作用域后变得有效。
- 它会保持自己的有效性直到自己离开作用域为止。
Rust在变量离开作用域时,会调用一个叫作
drop
的特殊函数。
3.移动
为了确保内存安全,同时也避免复制分配的内存,Rust在这种场景下会简单地将s1废弃,不再视其为一个有效的变量。因此,Rust也不需要在s1离开作用域后清理任何东西。
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
4. 克隆:深拷贝
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
5. Copy的trait
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
// x = 5, y = 5
以上代码没有调用clone,x在被赋值给y后也依然有效,且没有发生移动现象。这是为什么?
- 一旦某种类型拥有了Copy这种trait,那么它的变量就可以在赋值给其他变量之后保持可用性。
- 如果一种类型本身或这种类型的任意成员实现了Drop这种trait,那么Rust就不允许其实现Copy这种trait。
- 一些拥有Copy trait的类型
- 所有的整数类型,诸如u32。
- 仅拥有两种值(true和false)的布尔类型:bool。
- 字符类型:char。
- 所有的浮点类型,诸如f64。
- 如果元组包含的所有字段的类型都是Copy的,那么这个元组也是Copy的。例如,(i32, i32)是Copy的,但(i32, String)则不是。
引用与借用
(1)引用
let s1 = String::from("hello");
let len = calculate_length(&s1);
...
fn calculate_length(s: &String) -> usize { // s 是一个指向 String 的引用
s.len()
} // 到这里,s离开作用域。但是由于它并不持有自己所指向值的所有权,
//所以没有什么特殊的事情会发生
- 这些&代表的就是引用语义,它们允许你在不获取所有权的前提下使用值。
- 与变量类似,引用是默认不可变的,Rust不允许我们去修改引用指向的值。以下编译出错。
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
(2)可变引用
- 首先,我们需要将变量s声明为mut,即可变的。其次,我们使用&mut s来给函数传入一个可变引用,并将函数签名修改为some_string: &mut String来使其可以接收一个可变引用作为参数。
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
- 对于特定作用域中的特定数据来说,一次只能声明一个可变引用。
cannot borrow 's' as mutable more than once at a time...
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
好处:避免数据竞争。
数据竞争(data race)与竞态条件十分类似,它会在指令满足以下3种情形时发生:
• 两个或两个以上的指针同时访问同一空间。
• 其中至少有一个指针会向空间中写入数据。
• 没有同步数据访问的机制。
(3)悬垂引用
使用拥有指针概念的语言会非常容易错误地创建出悬垂指针。这类指针指向曾经存在的某处内存地址,但该内存已经被释放掉甚至是被重新分配另作他用了。而在Rust语言中,编译器会确保引用永远不会进入这种悬垂状态。假如我们当前持有某个数据的引用,那么编译器可以保证这个数据不会在引用被销毁前离开自己的作用域。