返回介绍

4.2 引用和借用

发布于 2024-10-13 11:25:28 字数 7745 浏览 0 评论 0 收藏 0

引用 (reference)是非所有(non-owning)指针类型。它不获取所有权,坚持单一所有和单一职责,解决共享访问障碍。创建引用的操作称为 借用 (borrow),意图是访问值,但不对其生命周期负责。

将所有权转移称作 值传递 (by value),而将借用称作 引用传递 (by reference)。

  • 共享引用&T ,不可变。
  • 可变引用&mut T ,独占。
fn main() {
  let a = 100;

  let r: &i32 = &a;      // 共享引用,不可变。
  let r2 = r;        // 共享引用复制。

  assert_eq!(*r, a);     // 解引用(dereferencing references)。
  assert_eq!(*r2, a);
}
fn main() {
  let mut a = 100;
  
  let r: &mut i32 = &mut a;  // 可变引用。
  *r += 1;

  assert_eq!(*r, 101);
}

可变引用的目标也必须是可变声明。

fn main() {
  let a = 100;
  let _r = &mut a;
       ^^^^^^ cannot borrow `a` as mutable, 
          as it is not declared as mutable
}

可获取匿名变量、字面量,以及表达式值引用。

fn test() -> i32 {
  100
}

fn main() {
  let r = &test();
  assert_eq!(*r, 100);

  let r = &200;
  assert_eq!(*r, 200);
}

支持多级引用。

fn main() {
  let p = 100;

  let r: &i32 = &p;
  let rr: &&i32 = &r;

  assert_eq!(*rr, r);
  assert_eq!(**rr, 100);
}

通常,需要显式使用 &* 操作符执行借用和解引用操作。如下状况,可自动解引用:

  • 访问数组(元组)元素。
  • 访问结构字段。
  • 以引用比较目标值。
  • 方法隐式借用( self )。
fn main() {
  let t = (1, 2, 3);
  let d = [1, 2, 3];

  let rt = &t;
  let rd = &d;

  assert_eq!(rt.2, 3);  // (*rt).2
  assert_eq!(rd[1], 2);   // (*rd)[1]
}
fn main() {
  struct Point { x: i32, y: i32 }
  let p = Point { x: 100, y: 200 }; 

  let r: &Point = &p;
  let rr: &&Point = &r;
  let rrr: &&&Point = &rr;

  assert_eq!((***rrr).y, 200);  // 显式解引用。
  assert_eq!(rrr.y, 200);     // 隐式解引用。
}
fn main() {
  let x = 10;
  let y = 10;

  let rx = &x;
  let ry = &y;

  assert!(rx <= ry);     // (*rx) <= (*ry)。
  assert!(rx == ry);     // 比较目标值,而非引用自身。

  let rrx = &rx;
  let rry = &ry;

  assert!(!(rrx > rry));  // 比较目标值。

  // assert!(rx == rry);  // 类型不同,无法比较。
  //      ^^ no implementation for `{integer} == &{integer}`

  assert!(!std::ptr::eq(rx, ry));  // 原始指针(内存地址)比较。
}

实现

引用与原始指针(raw)的区别:引用必须有效,不能为空,不能将某个整数值转换为引用(非安全代码除外)。另外,编译期会对引用和所有权进行生命周期(lifetime)安全检查。

实现层面,引用可能是胖指针(fat)。例如,切片(slice)和特征(trait)的引用是结构体。可用 Option<&T>::None 表示语义层面的无指向或无效引用。

fn main() {
  let mut a = 0;
  let _r = &mut a;
}
(gdb) disass/s

1  fn main() {

2    let mut a = 0;
=> 0x00005555555591f4 <+4>:    mov  DWORD PTR [rsp+0x4],0x0

3    let _r = &mut a;
   0x00005555555591fc <+12>:  lea  rax,[rsp+0x4]   ; 底层实现和指针一致。
   0x0000555555559201 <+17>:  mov  QWORD PTR [rsp+0x8],rax

4  }

引用不获取所有权,自然也不关心释放。

fn test(s: &mut String) {
  s.push_str("!!!") ;
}

fn main() {
  let mut s = String::from("hello");
  test(&mut s);
}
Dump of assembler code for function demo::main:
   0x000055555555be60 <+0>:    sub  rsp,0x28

   0x000055555555be64 <+4>:    lea  rax,[rip+0x3227c] # 0x55555558e0e7
=> 0x000055555555be6b <+11>:  mov  rdi,rsp
   0x000055555555be6e <+14>:  mov  rsi,rax
   0x000055555555be71 <+17>:  mov  edx,0x5
   0x000055555555be76 <+22>:  call   0x55555555afc0 <...::from>

   # 引用作为参数。
   0x000055555555be7b <+27>:  mov  rdi,rsp
   0x000055555555be7e <+30>:  call   0x55555555be40 <demo::test>

   # 由拥有者负责释放。
   0x000055555555be88 <+40>:  call   0x55555555b360 <...::drop_in_place>

   0x000055555555be8d <+45>:  add  rsp,0x28
   0x000055555555be91 <+49>:  ret  
End of assembler dump.


Dump of assembler code for function demo::test:
   0x000055555555be48 <+8>:    mov  QWORD PTR [rsp],rdi
=> 0x000055555555be4c <+12>:  mov  rsi,rax
   0x000055555555be4f <+15>:  mov  edx,0x3
   0x000055555555be54 <+20>:  call   0x55555555af60 <...::push_str>
End of assembler dump.

引用总是安全的,不会造成悬垂指针或野指针,所有权机制也不存在多重释放。

引用声明 &T ,指针 *T

fn main() {

  // --- reference --------------------------

  let mut x = 100;
  let r : &mut i32 = &mut x;

  *r += 1;
  println!("{:?}", *r);  // 引用是安全操作!

  // --- raw-pointer ------------------------

  let mut y = 200;
  let p : *mut i32 = &mut y;

  unsafe {         // 指针不安全!
    *p += 1;
    println!("{:?}", *p);
  }
}

规则

引用自带规则,需要编译器作出检查。

  • 借用不得超出所有者有效范围(socpe)。
  • 既定范围内,只有多个共享引用,或只有一个可变引用独占。
  • 可变引用失效(超出范围或不再使用)前,不能访问原变量。
  • 所有引用失效前,锁定值变量,不可修改、转移或重新赋值。

可按以下逻辑理解;

  • 只有一个所有者。
  • 其他人可以借用:
  • 可以有多人(含所有者)读( &T )。
  • 只能有一个写( &mut T ),其他人(含所有者)不能掺合。
  • 只要引用还在,所有者就不得修改、释放、转移或重新赋值。

可变引用的独占限制,可有效规避数据竞争(data race)。

值有效范围。

fn main() {
  let y: &i32;
  {
    let x = 5;
    y = &x;
      ^^ borrowed value does not live long enough
  }
  - `x` dropped here while still borrowed

  println!("{}", y);  
}

变量锁定。

在引用未失效的情况下,改变原绑定,会导致不一致行为。这与其他语言有很大区别。

fn main() {
  let mut s = String::from("abc");
  let r = &s;
      -- immutable borrow occurs here

  s.push_str("def");   // 引用未失效,不能修改。
  ^^^^^^^^^^^^^^^^^ mutable borrow occurs here

  println!("{:?}", r);
           -- immutable borrow later used here
}
fn main() {
  let mut a = 1;
  
  let r = &a;     // 或 &mut a。
       -- borrow of `a` occurs here
  
  a = 50;       // 引用未失效,不能重新赋值。
  ^^^^^^ assignment to borrowed `a` occurs here

  println!("{}", r);
           - borrow later used here
}
fn main() {
  let mut a = 1;

  let r = &a;     // 或 &mut a
  println!("{}", r);  // 后续不再使用。

  a = 50;       // OK!
}

不能 “同时” 有可变和不可变借用行为。

fn main() {
  let mut x = 1;
  let r = &mut x;

  println!("{}{}", x, r);
           ^  - mutable borrow later used here
           |
           immutable borrow occurs here
}
fn main() {
  let mut x = 5;
  let r = &mut x;

  *r += 1;

  println!("{}", x);
           ^ immutable borrow occurs here 
  
  println!("{}", *r);
           -- mutable borrow later used here
}
fn main() {
  let mut x = 5;

  {
    let r = &mut x;
    *r += 1;
    println!("{}", *r);
  }               // 可变借用结束。

  println!("{}", x);
}

前一可变借用未失效前,不能再次借用(包括共享)。

fn main() {
  let mut x = 1;
  let r1 = &mut x;
  let r2 = &mut x;

  println!("{}{}", r1, r2);
}
error[E0499]: cannot borrow `x` as mutable more than once at a time
 --> src/main.rs:4:14
  |
3 |   let r1 = &mut x;
  |        ------ first mutable borrow occurs here
4 |   let r2 = &mut x;
  |        ^^^^^^ second mutable borrow occurs here
5 | 
6 |   println!("{}{}", r1, r2);
  |            -- first borrow later used here

引用可变

值可变和引用自身可变的区别。

引用自身也是一个对象,有存储空间。和普通变量一样, let mut ref = ... 表示引用可变。声明值可变,还是引用自身可变,在于 mut 位置。冒号左侧:引用可变;右侧:值可变。

fn main() {
  let mut x = 100;

  let r = &mut x;     // 值可变。 let r: &mut i32 = &mut x;
  *r += 1;

  assert_eq!(*r, 101);
}
fn main() {
  let x = 100;
  let y = 200;

  let mut r = &x;    // 引用自身可变。 let mut r: &i32 = &x;
  assert_eq!(*r, x);

  r = &y;        // 引用绑定新对象。
  assert_eq!(*r, y);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文