文章目录
1.对可Copy类型数据,引用时,不会拷贝一份数据到栈中;复制时,会在其他位置拷贝一份数据到栈中
use std::ptr::addr_of;
use std::mem::size_of_val;
#[allow(unused)]
fn main() {
let x: i8 = 5;
let y: &i8 = &x;//引用
let z: i8 = x;//复制
let z1: i8 = x;//复制
println!("{:?} {:?} {:?} {:?}",addr_of!(x),addr_of!(y),addr_of!(z),addr_of!(z1));
println!("{} {} {} {}", size_of_val(&x),size_of_val(&y),size_of_val(&z),size_of_val(&z1)); // 输出 value 占用的字节数
}
输出:
0x7ffeb7efcfc7 0x7ffeb7efcfc8 0x7ffeb7efcfd6 0x7ffeb7efcfd7
1 8 1 1
- 已知,i8类型的大小为1B,&i8 是对 i8 类型的不可变引用,引用的大小与它所引用的数据类型的大小无关,而是取决于平台的指针大小。在 32 位架构上,&i8 的大小通常是 4 字节,而在 64 位架构上,大小是 8 字节。这里我的电脑是64位,所以这里&i8类型大小为8B。
- addr_of!函数的输出可知x,y,z,z1在内存中的地址
- size_of_val(&value)函数可以看出具体值而不是某一类型在内存中所占字节数大小
- 综上所述,可以看出,对于可Copy的数据类型,当用引用&时,会在栈中生成一个指针指向原数据,而不会拷贝一份数据,指针大小为4B或者8B;当复制时,会在栈中拷贝一份数据
- 另外,从addr_of!函数的输出看出,y和z在内存中的位置并没有挨着,这应该是为了符合边界对齐的原则
2.在 Rust 中,当你通过索引访问数组或向量的元素时,你得到的是该元素的不可变引用
数组和向量是固定大小的集合,并且它们的元素存储在栈上,它们提供的索引操作符返回的是元素的不可变引用。
并且在Rust语言圣经中,在Vector动态数组的学习中,该书给了两种方式实现从Vector中读取元素
- 通过下标索引访问
- 使用 get 方法
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("第三个元素是 {}", third);
match v.get(2) {
Some(third) => println!("第三个元素是 {third}"),
None => println!("去你的第三个元素,根本没有!"),
}
和其它语言一样,集合类型的索引下标都是从 0 开始,&v[2] 表示借用 v 中的第三个元素,最终会获得该元素的引用。而 v.get(2) 也是访问第三个元素,但是有所不同的是,它返回了 Option<&T>,因此还需要额外的 match 来匹配解构出具体的值。
对于不可Copy的类型呢,这里用数组中放String类型举例:
#[allow(unused)]
fn main() {
let x = "x".to_string();
let y = x;//成功执行
let arr = [1, 2];
let (first, second) = (arr[0], arr[1]);//成功执行
let arr = ["1".to_string(), "2".to_string()];
let (first, second) = (arr[0], arr[1]);//失败
}
报错如下:
error[E0508]: cannot move out of type `[String; 2]`, a non-copy array
--> src/main.rs:10:34
|
10 | let (first, second) = (arr[0], arr[1]);
| ^^^^^^
| |
| cannot move out of here
| move occurs because `arr[_]` has type `String`, which does not implement the `Copy` trait
可以看出,let y = x可以正确执行,这里是将x的所有权转移给y,之后x不能再用。
当arr = [1, 2]时,let (first, second) = (arr[0], arr[1]);没有&arr[0],&arr[1]也能成功执行。
当arr = [“1”.to_string(), “2”.to_string()]时,我们来分析let (first, second) = (arr[0], arr[1]); 这里arr[0]通过索引访问数组元素,期望得到&String类型,实际上得到String类型,那么这条语句不就是相当于把两个String类型数据的所有权给first和second嘛,转移所有权也不难嘛,前两条代码不也执行的好好的,想法很美好,实际上不能成功。
Rust为了安全考虑,通过索引访问数组元素希望得到的是String的不可变引用(&String),而不是String的所有权。Rust不允许直接从一个不可变引用中复制所有权,因为引用本身并不拥有数据的所有权。因此会发生冲突报错,报错提示我们String没有实现Copy特征,这同样也是为什么当arr = [1,2]时,let (first, second) = (arr[0], arr[1]);没有用&arr[0],&arr[1],但是仍然能够成功执行的原因,因为整形类型实现了Copy特征,整形类型没有所有权的转移,可以直接在栈中深拷贝数据,不会有安全问题。
最后,在Rust中,如果你有一个包含String类型元素的动态数组(通常是通过Vec<String>来表示),你可以通过几种方式来转移元素的所有权:
使用 remove 方法:
remove 方法可以从向量中移除一个元素,并返回该元素的所有权。你可以指定要移除的索引,例如移除第一个元素:
fn main(){
let mut v = vec!["hello".to_string(), "world".to_string()];
let removed_element = v.remove(0); // 移除第一个元素,并获取所有权
println!("Removed element: {}", removed_element);
}
使用模式匹配进行解构:
如果你知道向量至少有一个元素,你可以使用模式匹配来转移所有权:
fn main(){
let mut v = vec!["hello".to_string(), "world".to_string()];
let first_element = match v.pop() {
Some(s) => s, // pop 方法移除并返回向量中的最后一个元素
None => panic!("Vector is empty"),
};
println!("First element: {}", first_element);
}
3.切片操作
fn main() {
let mut x = "a".to_string();
let y = x[..].to_string(); // 创建x的一个副本
println!("x: {}", x);
println!("y: {}", y);
// 修改x和y,看看它们是否独立
x.push_str("bc");
println!("x after push: {}", x);
// y 没有被修改,因为它是x的一个副本
println!("y remains: {}", y);
}
在 Rust 中,当你使用切片操作 x[…] 并将其转换为一个新的 String 时,如 let y = x[…].to_string();,你实际上是在创建 x 的一个完整副本,而不是一个指向 x 原始数据的引用。因此,y 所指向的数据和 x 所指向的数据不是同一个。
输出如下:
x: a
y: a
x after push: abc
y remains: a
4.可变引用与不可变引用不能同时存在
更确切的说是,同一时刻,只能存在要么一个可变引用, 要么任意多个不可变引用。
不是不能同时声明可变引用与不可变引用,而是要保证对他们的使用要在正确的作用域中。
注意,引用的作用域 s 从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号 }
另外注意,上面说的引用的作用域和变量的作用域还不太一样,通过逐步调试可以得出,变量出作用域是真的会drop释放资源的;而当引用离开它最后一次使用的地方后不会drop,在调试栏变量中还存在。这里引用的作用域更多的还是为了更好遵守借用规则而引出的技巧。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
println!("{}, {}, and {}", r1, r2, r3);//这一时刻,同时使用了可变引用与不可变引用
//r1,r2,r3的作用域在这里结束
}
报错如下:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
// 无法借用可变 `s` 因为它已经被借用了不可变
--> src/main.rs:6:14
|
4 | let r1 = &s; // 没问题
| -- immutable borrow occurs here 不可变借用发生在这里
5 | let r2 = &s; // 没问题
6 | let r3 = &mut s; // 大问题
| ^^^^^^ mutable borrow occurs here 可变借用发生在这里
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here 不可变借用在这里使用
如果改称如下代码:
#[allow(unused)]
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
}
//r1,r2,r3作用域在这里结束
则不会报错,这里虽然分别声明了对s的不可变引用和可变引用,但是全程没有使用他们,没有使用,也就没有冲突,也就不会报错。
再看下面:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1,r2作用域在这里结束
let r3 = &mut s;
println!("{}", r3);
//r3作用域在这里结束
}
在使用他们的时候没有发生冲突,正确运行。
再看下面:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
println!("{} and {}", r1, r2);//这一时刻,使用了对s的不可变引用,这一时刻r3还没有消失,因此,在这一时刻,可变引用与不可变引用同时存在了,因此报错
// r1,r2作用域在这里结束
println!("{}", r3);
//r3作用域在这里结束
}
报错如下:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:23:14
|
21 | let r1 = &s;
| -- immutable borrow occurs here
22 | let r2 = &s;
23 | let r3 = &mut s;
| ^^^^^^ mutable borrow occurs here
24 | println!("{} and {}", r1, r2);//这一时刻,使用了对s的不可变引用,这一时刻r3还没 ...
| -- immutable borrow later used here
总结:为了遵守Rust语言的借用规则,则需要搞清楚可变与不可变引用的作用域,才能避免出错
1319

被折叠的 条评论
为什么被折叠?



