所有权
所有权的概念是与一般的编程语言最大的不同,rust 整个语言的设计都是围绕所有权来的,在学习所有权的时候需要反复练习,去体会其中的设计,书中总结了三个原则:
变量作用域
x 在进入作用域后变得有效。
它会保持自己的有效性直到自己离开作用域为止。
fn main() {
println!("{}", x); // 报错,x还未初始化
{ // 这里是一个作用域的开始
let x = "string";
println!("{}", x); // "string"
} // 这里是一个作用域的结束,x将被回收
println!("{}", x); // 报错,x不存在
}
复制代码
String 类型
字符串的堆存储和栈存储
初始化一个存放在堆中的字符串:
// 创建空字符串
let mut s1 = String::new();
// 从字面量创建字符串
let mut s2 = String::from("hello");
// 追加单个char
s1.push('h');
// 可以追加一个字符串
s2.push_str("world");
println!("{}-{}", s1, s2); // h-helloworld
复制代码
初始化一个存放在栈中的字符串:
// 字面量声明的字符串会存在栈中,是不可变的
let mut x = "string";
x.push('x'); // 报错: 不存在 'push' 方法
复制代码
所有权转移
存放在堆中的数据会发生所有权的转移:
// 初始化一个堆中的字符串
let s1 = String::from("1");
// 这里将s1的指针转移给了s2,s1会被释放
let s2 = s1;
println!("{}", s2) // "1"
// 尝试获取s1
println!("{}" s1) // 报错,s1的数据已经移动给了s2
复制代码
栈中的数据会发生复制,而不会转移所有权:
// 初始化一个栈的字符串
let s1 = "hello";
// s2会复制给s1
let s2 = s1;
// 获取s1和s2
println!("{}" s1); // "hello"
println!("{}" s2); // "hello"
复制代码
使用数据克隆复制堆中数据:
let s1 = String::from("hello");
// 复制s1的数据给s2
let s2 = s1.clone();
// 获取s1和s2
println!("{}" s1); // "hello"
println!("{}" s2); // "hello"
复制代码
存在栈中的数据类型,是否使用 clone 是没有区别的:
let s1 = "hello";
// 复制s1的数据给s2
let s2 = s1.clone();
// 等价于
// let s2 = s1;
// 获取s1和s2
println!("{}" s1); // "hello"
println!("{}" s2); // "hello"
复制代码
事实上上面只是拿字符串来举例,对于数字和 char 来说,也是存在栈中的,同样会进行数据复制,而不是所有权的转移。
关于元组
如果元组中所有成员都是栈中数据,那元组同样可以复制:
// 定义一个包含字符串,char,数字类型的元组
let t1 = ("abc", 'c', 1);
// t1复制给t2
let t2 = t1;
// 尝试获取
println!("{:?}", t1); // ("abc", 'c', 1)
println!("{:?}", t2); // ("abc", 'c', 1)
复制代码
如果成员中有一个数据不是可复制的,那么元组也不可能复制,只是简单的所有权转移:
// 初始化一个包含堆中数据的元组
let t1 = ("abc", 'c', String::new());
// 这里t1会把所有权转移给t2
let t2 = t1;
// 尝试获取
println!("{:?}", t2);
println!("{:?}", t1); // 报错,t1的所有权已经移动给了t2
复制代码
允许移动成员,但是移动后不能再使用:
let t1 = (String::from("hello"), String::from("world"));
// 将第一个成员的所有权转移给s
let s = t1.0;
println!("{}", s); // "hello"
println!("{}", t1.1); // "world"
println!("{:?}", t1); // 报错,成员的所有权已经被移动了
println!("{}", t1.0); // 报错,成员的所有权已经被移动了
复制代码
数组成员如果是栈中数据,那数组同样可以复制:
let t1 = (1, 2);
// 复制第一个成员给t2
let t2 = t1.0;
println!("{}", t2); // 1
println!("{}", t1.0); // 1
println!("{}", t1.1); // 2
复制代码
数组的规则与元组的规则相同,这里不再介绍,可以自行练习
所有权与函数参数
调用函数时,对函数的传参也会转移所有权:
// 由于函数参数类型是堆中数据,所以会发生所有权转移
fn take_ownership(s: String) {
println!("{}", s) // "hello"
}
// 定义一个堆中数据
let s = String::from("hello");
// 调用函数会将s的所有权转移给函数
take_ownership(s);
// 尝试获取s
println!("{}", s); // 报错,所有权被take_ownership拿走了
复制代码
栈中的数据会发生复制:
// 由于函数参数类型是栈中数据,所以会发生复制
fn makes_copy(n: i32) {
println!("{}", n) // 5
}
// 定义一个栈中数据
let num = 5;
// 调用函数会对num进行复制
makes_copy(num);
// num仍然有效
println!("{}", num); // 5
复制代码
返回值与作用域
当一个持有堆数据的变量离开函数作用域时,它的数据就会被清理回收,但是可以通过返回值将数据返回而不被销毁:
// 函数中创建一个堆中数据字符串,并返回所有权
fn gives_owner_ship() -> String {
let s = String::from("hello");
s
}
// 函数接受一个字符串数据的所有权,然后直接返回
fn takes_and_gives_back(s: String) -> String {
s
}
// 获取函数中创建的字符串的所有权
let s1 = gives_owner_ship();
// 将s2的所有权转移给函数,再通过函数返回值将所有权转移给s3
let s2 = String::from("world");
let s3 = takes_and_gives_back(s2);
println!("{}", s1); // "hello"
println!("{}", s3); // "world"
println!("{}", s2); // 报错, s2的所有权转移到了s3
复制代码
利用返回值拿回所有权:
fn get_lenth(s: String) -> (String, usize) {
let len = s.len(); // 获取s的长度
(s, len) // 将s的所有权和长度返回
}
let s1 = String::from("hello");
// 使用返回值重新拿到所有权
let (s1, len) = get_lenth(s1);
println!("{}-{}", s1, len); // "hello-5"
复制代码
引用变量
数据的所有权转移会降低开发的灵活性,所有有了引用的概念来解决这个问题:
let s1 = String::from("hello");
// 使用&符号创建两个对s1的引用,不会获取s1的所有权,可以将引用理解成一个指针
let s2 = &s1;
let s3 = &s2;
// 获取
println!("{}-{}-{}", s1, s2, s3); // "hello-hello-hello"
复制代码
函数同样支持引用类型:
// 指定引用类型的参数
fn get_length(s: &String) -> usize {
// 这里的 s 指向 s1 指向 String真实数据
// 由于s没有取得String数据的所有权,所以这里不需要使用返回值归还所有权
s.len()
}
let s1 = String::from("hello");
// 将s1的引用传给函数
let len = get_length(&s1);
// s1仍可使用
println!("{}-{}", s1, len); // "hello-5"
复制代码
使用 &可以引用数据,但是不可以对数据进行改变:
let s1 = String::from("hello");
let s2 = &s1;
// 通过s2试图改变数据
s2.push("world"); // 报错,s2是一个不可变引用,不能对数据进行更改
复制代码
使用可变引用对数据进行更改:
// 使用&mut将参数声明为可变引用
fn get_length(s: &mut String) -> usize {
s.push_str("world");
s.len()
}
// 声明可变数据
let mut s1 = String::from("hello");
// 传入可变引用变量
let len = get_length(&mut s1);
// s1发生了数据变更
println!("{}-{}", len, s1); // "10-helloworld"
复制代码
对于单个作用域中的数据来说,一次只能声明一个可变引用:
let mut s1 = String::from("hello");
let s2 = &mut s1;
let s3 = &mut s1 // 报错,不能有多个可变引用
复制代码
在单个作用域中也不能存在可变引用和可变引用的函数传参:
let mut s1 = String::from("hello");
let s2 = &mut s1;
let len = get_length(&mut s1); // 报错,同样不能有多个可变引用,因为传参和s2都属于可变引用
复制代码
可变与不可变引用不能同时存在:
let mut s1 = String::from("hello");
// 创建不可变引用
let s2 = &s1; // 报错,不能将s1进行不可变引用,因为下边有可变引用
// 创建可变引用
let mut s3 = &mut s1; // 报错,不能将s1进行可变引用,因为上边有不可变引用
// 前提是下面有使用到上边的值,否则就不会报错
println!("{}", s1);
println!("{}", s2);
println!("{}", s3);
复制代码
引用的规则
评论