引用¶
let value: i32 = 123;
let refer = &value;
//只读引用
let refer_mut = &mut value;
//可变引用
raw pointer¶
fn main() {
let mut x = 10;
let ptr_x = &mut x as *mut i32;
let y = Box::new(20);
let ptr_y = &*y as *const i32;
unsafe {
*ptr_x += *ptr_y;
}
assert_eq!(x, 30);
println!("{}", x);
}
let mut x = 10:可变绑定,x是栈上的i32,值为 10。&mut x:对x的可变引用。as *mut i32:把“可变引用”强制转换成“可变裸指针”类型*mut i32。-
ptr_x:保存的是x的地址,通过它可以修改x(在 unsafe 里解引用时)。 -
let y = Box::new(20):y拥有一个堆上分配的i32,值为 20。y是Box<i32>。 *y:解引用Box,得到堆上的i32值(不移动所有权)。&*y:对这个i32的不可变引用,即&20(指向堆上那块内存)。as *const i32:把引用转成不可变裸指针*const i32。ptr_y:指向堆上那个 20 的只读裸指针。
这里没有消费 y,所以 Box 仍然持有堆内存,ptr_y 指向的内存在整个 y 存活期间都是有效的。
unsafe { ... }:裸指针解引用必须在unsafe里,由程序员保证安全。*ptr_y:读ptr_y指向的值 → 20。*ptr_x:读并写ptr_x指向的值(即x)。*ptr_x += *ptr_y:等价于x = x + 20,所以执行后x从 10 变成 30。
内存关系简图¶
栈:
x: 10 → (unsafe 后变为 30)
ptr_x: 指向 x 的地址
y: Box → 堆上某地址
堆:
某个 i32: 20
ptr_y 指向这里
ptr_x 指向栈上的 x,ptr_y 指向 Box 管理的堆上的 20;unsafe 里用这两个裸指针完成“把堆上的值加到栈上的变量上”。
安全性说明¶
这段代码在当前写法下是安全的,因为:
ptr_x:来自&mut x,指向当前栈上的有效变量,且没有别的引用同时存在。ptr_y:来自&*y,在y仍存在时指向其堆内存,生命周期正确。- 无重叠:一个指栈、一个指堆,没有同一块内存被同时用多种方式访问的问题。
如果后面在别处把 y 给 drop 掉,再在 unsafe 里用 *ptr_y 就会变成悬垂指针,属于未定义行为。所以“安全”依赖于:只在 y 和 x 都仍然有效的作用域内使用这两个指针。
小结¶
| 变量 | 类型 | 含义 |
|---|---|---|
x |
i32 |
栈上整数,初值 10,后被改为 30 |
ptr_x |
*mut i32 |
指向 x,用于写 |
y |
Box<i32> |
拥有堆上的 20 |
ptr_y |
*const i32 |
指向堆上的 20,只读 |
*ptr_x += *ptr_y 的含义就是:用裸指针把堆上的 20 加到栈上的 x 上,等价于 x += 20
array 和 vector¶
let sa: [u16] = a[0..2]
[u16]不能作为变量类型
[u16] 是 unsized type, 编译器不知道要占多少字节,无法分配栈帧,所以不能用来声明局部变量。
- 可以: [u16; 2] (固定长度数组)
- &[u16] (切片引用)
- &mut [u16]
- 右边:
a[0..2]的类型是&[u16],不是[u16]
对数组或切片做范围索引时,得到的是切片引用,不是 “裸切片”
| 类型 | 含义 | 能否修改元素 |
|---|---|---|
[u16] |
切片(无固定长度) | 不能单独作为变量类型 |
&[u16] |
对切片的不可变引用 | 否(只读) |
&mut [u16] |
对切片的可变引用 | 是 |
[u16; N] |
固定长度数组 | 若绑定为 mut 则可以 |
在数组/向量上生成切片
fn main() {
let v: Vec<u16> = vec![0, 1, 2, 3, 4];
let len = v.len();
let s = &v; // s的类型为 &Vec<u16>; 不是切片
println!("{:?}", s); //=> [0, 1, 2, 3, 4]
let s: &[u16] = &v; //等价于 &v[0..len]; 是切片,编译器进行了类型转换
println!("{:?}", s); //=> [0, 1, 2, 3, 4]
let s = &v[..]; //等价于 &v[0..len]; 是切片
}
利用切片编写同时适用于数组和向量的函数:
fn main() {
let a: [u16; 4] = [0, 1, 2, 3];
let v: Vec<u16> = vec![0, 1, 2, 3];
print(&a);
print(&v);
}
fn print(s: &[u16]) {
for e in s {
print!("{} ", e);
}
println!("");
}
String¶
string
- 一种特殊的 Vec<u8>
- 特殊性:向量中存储的 <u8> 是合法的 utf-8 序列
.len() 方法返回字符串占用的字节(byte)数量
fn main() {
let mut z = String::from("English");
z[0] = 'e';
}
报错,因为 String 中没有 [] 操作符,虽然 String 是一种特殊的 Vec