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);
}