Skip to content

引用与指针

引用

引用(reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同,引用确保指向某个特定类型的有效值。

rust
fn main() {
    let x = 5;
    let y = &x;
}

变量 x 存放了一个 i32 值 5。y 是 x 的一个引用。

想要使用引用的内容,可以使用解引用运算符解出引用所指向的值。

不可变引用

下面的代码,我们用 s1 的引用作为参数传递给 calculate_length 函数,而不是把 s1 的所有权转移给该函数:

rust
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

在上面的代码中,calculate_length 的参数使用引用类型 &String。这里,& 符号即是引用,它们允许你使用值,但是不获取所有权。

通过 &s1 语法,我们创建了一个指向 s1 的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。

同理,函数 calculate_length 使用 & 来表明参数 s 的类型是一个引用:

rust
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
    s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
  // 所以什么也不会发生

如果尝试修改借用的变量呢?

rust
fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

很不幸,这样无法通过:

error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- help: consider changing this to be a mutable reference: `&mut String`
                           ------- 帮助:考虑将该参数类型修改为可变的引用: `&mut String`
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
                     `some_string`是一个`&`类型的引用,因此它指向的数据无法进行修改

正如变量默认不可变一样,引用指向的值默认也是不可变的,没事,来一起看看如何解决这个问题。

可变引用

使用关键字 mut ,即可修复上面代码的错误:

rust
fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

首先,声明 s 是可变类型,其次创建一个可变的引用 &mut s 和接受可变引用参数 some_string: &mut String 的函数。

可变引用同时只能存在一个

rust
// 错误示例
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;

println!("{}, {}", r1, r2);

这段代码出错的原因在于,第一个可变借用 r1 必须要持续到最后一次使用的位置 println!,在 r1 创建和最后一次使用之间,我们又尝试创建第二个可变借用 r2。这个特性就是Rust的借用检查。

这种限制的好处就是使 Rust 在编译期就避免数据竞争,数据竞争可由以下行为造成:

  • 两个或更多的指针同时访问同一数据
  • 至少有一个指针被用来写入数据
  • 没有同步数据访问的机制

数据竞争会导致未定义行为,这种行为很可能超出我们的预期,难以在运行时追踪,并且难以诊断和修复。而 Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!

很多时候,大括号可以帮我们解决一些编译不通过的问题,通过手动限制变量的作用域:

rust
let mut s = String::from("hello");

{
    let r1 = &mut s;

} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用

let r2 = &mut s;

可变引用与不可变引用不能同时存在

下面的代码会导致一个错误:

rust
// 错误示例
let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题

println!("{}, {}, and {}", r1, r2, r3);

其实这个也很好理解,正在借用不可变引用的用户,肯定不希望他借用的东西,被另外一个人莫名其妙改变了。多个不可变借用被允许是因为没有人会去试图修改数据,每个人都只读这一份数据而不做修改,因此不用担心数据被污染。

注意,引用的作用域 s 从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号 }。

指针

注:本节内容可能对于新手理解比较难,可以在完成本课程后再学习了解

Rust中的指针类型包括:引用(可变和不可变)、裸指针、智能指针。

引用我们在上面介绍了,智能指针将在之后的章节介绍。本节重点介绍一下Rust中的裸指针。

裸指针是没有安全性或可用性(liveness)保证的指针。 裸指针写为 *const T 或 *mut T,例如,*const i32 表示指向 32-bit 有符号整数的裸指针。

创建裸指针有很多方式:

1.强制引用 (&T) 或可变引用 (&mut T)。

rust
let my_num: i32 = 10;
let my_num_ptr: *const i32 = &my_num;
let mut my_speed: i32 = 88;
let my_speed_ptr: *mut i32 = &mut my_speed;

2.使用智能指针Box<T>

into_raw 函数使用 box 并返回裸指针。它不会销毁 T 或释放任何内存。

rust
fn main() {
    let my_speed: Box<i32> = Box::new(88);
    let my_speed: *mut i32 = Box::into_raw(my_speed);
    
    // By taking ownership of the original `Box<T>` though
    // we are obligated to put it together later to be destroyed.
    unsafe {
        drop(Box::from_raw(my_speed));
    }  
}

请注意,此处对 drop 的调用是为了清楚起见 - 表示我们已经完成了给定值的操作,应将其销毁。

3.使用 ptr::addr_of! 创建

可以使用宏 ptr::addr_of! (对于 *const T) 和 ptr::addr_of_mut! (对于 *mut T),而不是强制引用裸指针。

这些宏允许您创建裸指针指向您无法创建引用的字段 (不会导致未定义的行为),例如未对齐的字段。 如果涉及包装的结构或未初始化的内存,这可能是必要的。

rust
#![allow(unused)]
fn main() {
    #[derive(Debug, Default, Copy, Clone)]
    #[repr(C, packed)]
    struct S {
        aligned: u8,
        unaligned: u32,
    }
    let s = S::default();
    let p = std::ptr::addr_of!(s.unaligned); // 不允许强制转换
}

4.从 C 获取裸指针。

rust
#![allow(unused)]
#![feature(rustc_private)]
extern crate libc;

fn main() {
    use std::mem;

    unsafe {
        let my_num: *mut i32 = libc::malloc(mem::size_of::<i32>()) as *mut i32;
        if my_num.is_null() {
            panic!("failed to allocate memory");
        }
        libc::free(my_num as *mut libc::c_void);
    }
}

对裸指针的解引用是非安全(unsafe)操作,可以通过重新借用裸指针(&* 或 &mut *)将其转换为引用。

在 Rust 代码中通常不鼓励使用裸指针;它们的存在是为了提升与外部代码的互操作性,以及编写对性能要求很高的函数或很底层的函数。

在比较裸指针时,比较的是它们的地址,而不是它们指向的数据。

当比较裸指针和动态尺寸类型时,还会比较它们指针上的附加/元数据。