4.2 引用和借用
引用 (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 = ℞ 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论