跳转至

引用

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 指向栈上的 xptr_y 指向 Box 管理的堆上的 20;unsafe 里用这两个裸指针完成“把堆上的值加到栈上的变量上”。


安全性说明

这段代码在当前写法下是安全的,因为:

  1. ptr_x:来自 &mut x,指向当前栈上的有效变量,且没有别的引用同时存在。
  2. ptr_y:来自 &*y,在 y 仍存在时指向其堆内存,生命周期正确。
  3. 无重叠:一个指栈、一个指堆,没有同一块内存被同时用多种方式访问的问题。

如果后面在别处把 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]
可以过编译吗?

  1. [u16] 不能作为变量类型

[u16] 是 unsized type, 编译器不知道要占多少字节,无法分配栈帧,所以不能用来声明局部变量。 - 可以: [u16; 2] (固定长度数组) - &[u16] (切片引用) - &mut [u16]

  1. 右边: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,但是为了保证 utf-8 序列的合法性,不允许 [],如果需要操作 char,可以使用 Vec