Rust所有权与借用检查器详解
2025.07.23
Rust
8 min
3.1k 字
// 目录 · contents
前言 所有权规则 移动语义(Move Semantics) Clone vs Copy 借用(Borrowing) 不可变借用(&T) 可变借用(&mut T) 借用规则 悬垂引用(Dangling Reference) 生命周期(Lifetimes) 生命周期省略规则(Lifetime
Elision) 结构体中的生命周期 ’static生命周期 NLL(Non-Lexical Lifetimes) 常见模式 模式1: 字符串处理 模式2: 迭代器与借用 模式3: 内部可变性 模式4: Cow(Clone on Write) 常见编译错误和解决方案 总结
前言
Rust的所有权系统是其最核心的特性,通过编译期检查实现内存安全,无需垃圾回收器。所有权、借用和生命周期三者协同工作,让Rust在保证安全的同时保持零成本抽象。本文将深入解析这些机制的原理和使用模式。
所有权规则
Rust的三条所有权规则:
每个值都有一个所有者(owner)
同一时刻只能有一个所有者
当所有者离开作用域,值被丢弃(drop)
graph TB
subgraph "栈内存 (Stack)"
S1[s1: ptr, len, cap]
S2[s2: ptr, len, cap]
end
subgraph "堆内存 (Heap)"
H1["hello"]
end
S1 -.->|"移动后s1无效"| H1
S2 -->|"s2拥有数据"| H1
style S1 fill:#e0e0e0,color:#999
1 2 3 4 5 6 7 8 9 10 11 12 fn main () { let s1 = String ::from ("hello" ); let s2 = s1; println! ("{}" , s2); }
移动语义(Move Semantics)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 let a = String ::from ("hello" );let b = a; fn take_string (s: String ) { println! ("{}" , s); } let s = String ::from ("world" );take_string (s); fn give_string () -> String { let s = String ::from ("returned" ); s }let s = give_string (); let x : i32 = 42 ;let y = x; println! ("x={}, y={}" , x, y);
Clone vs Copy
graph LR
subgraph "Copy (栈上复制)"
I1["i32: 42"] -->|位拷贝| I2["i32: 42"]
NOTE1[零成本,编译器自动处理]
end
subgraph "Clone (显式深拷贝)"
S1["String: ptr→heap"] -->|clone| S2["String: ptr→new heap"]
NOTE2[需要显式调用,可能昂贵]
end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let s1 = String ::from ("hello" );let s2 = s1.clone (); println! ("s1={}, s2={}" , s1, s2);#[derive(Clone)] struct Config { name: String , values: Vec <i32 >, }let config1 = Config { name: String ::from ("test" ), values: vec! [1 , 2 , 3 ], };let config2 = config1.clone ();
借用(Borrowing)
借用允许在不获取所有权的情况下使用值。
不可变借用(&T)
1 2 3 4 5 6 7 8 9 10 fn calculate_length (s: &String ) -> usize { s.len () }fn main () { let s = String ::from ("hello" ); let len = calculate_length (&s); println! ("'{}' has length {}" , s, len); }
可变借用(&mut T)
1 2 3 4 5 6 7 8 9 fn append_world (s: &mut String ) { s.push_str (" world" ); }fn main () { let mut s = String ::from ("hello" ); append_world (&mut s); println! ("{}" , s); }
借用规则
graph TB
RULE[借用规则] --> R1[规则1: 任意数量的不可变借用<br>&T + &T + &T = OK]
RULE --> R2[规则2: 同一时刻只能有一个可变借用<br>&mut T = OK<br>&mut T + &mut T = ERROR]
RULE --> R3[规则3: 不可变和可变借用不能共存<br>&T + &mut T = ERROR]
style R1 fill:#388e3c,color:#fff
style R2 fill:#f57c00,color:#fff
style R3 fill:#d32f2f,color:#fff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fn main () { let mut s = String ::from ("hello" ); let r1 = &s; let r2 = &s; println! ("{} and {}" , r1, r2); let r3 = &mut s; r3.push_str (" world" ); println! ("{}" , r3); }
悬垂引用(Dangling Reference)
1 2 3 4 5 6 7 8 9 10 11 fn dangle () -> &String { let s = String ::from ("hello" ); &s }fn no_dangle () -> String { let s = String ::from ("hello" ); s }
生命周期(Lifetimes)
生命周期注解告诉编译器多个引用之间的关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 fn longest <'a >(x: &'a str , y: &'a str ) -> &'a str { if x.len () > y.len () { x } else { y } }fn main () { let string1 = String ::from ("long string" ); { let string2 = String ::from ("xyz" ); let result = longest (string1.as_str (), string2.as_str ()); println! ("Longest: {}" , result); } }
生命周期省略规则(Lifetime
Elision)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fn first_word (s: &str ) -> &str { }impl Config { fn name (&self ) -> &str { &self .name } }fn longest (x: &str , y: &str ) -> &str { }
结构体中的生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 struct ImportantExcerpt <'a > { part: &'a str , }impl <'a > ImportantExcerpt<'a > { fn level (&self ) -> i32 { 3 } fn announce_and_return_part (&self , announcement: &str ) -> &str { println! ("Attention: {}" , announcement); self .part } }fn main () { let novel = String ::from ("Call me Ishmael. Some years ago..." ); let first_sentence ; { let i = novel.split ('.' ).next ().expect ("Could not find '.'" ); first_sentence = ImportantExcerpt { part: i }; } println! ("{}" , first_sentence.part); }
’static生命周期
1 2 3 4 5 6 let s : &'static str = "I have a static lifetime" ;
NLL(Non-Lexical Lifetimes)
Rust 2018引入的NLL让借用检查更加精确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fn example_old () { let mut data = vec! [1 , 2 , 3 ]; let first = &data[0 ]; println! ("{}" , first); }fn example_nll () { let mut data = vec! [1 , 2 , 3 ]; let first = &data[0 ]; println! ("{}" , first); data.push (4 ); println! ("{:?}" , data); }
graph TB
subgraph "Lexical Lifetimes (旧)"
L1["let first = &data[0]; // 借用开始"] --> L2["println!(first);"]
L2 --> L3["data.push(4); // ERROR: 借用仍在作用域内"]
L3 --> L4["} // 借用在这里结束"]
end
subgraph "Non-Lexical Lifetimes (NLL)"
N1["let first = &data[0]; // 借用开始"] --> N2["println!(first); // 借用在这里结束"]
N2 --> N3["data.push(4); // OK: 借用已结束"]
N3 --> N4["}"]
end
style L3 fill:#d32f2f,color:#fff
style N3 fill:#388e3c,color:#fff
常见模式
模式1: 字符串处理
1 2 3 4 5 6 7 8 9 10 fn greet (name: &str ) { println! ("Hello, {}!" , name); }let owned = String ::from ("World" );let literal = "World" ;greet (&owned); greet (literal);
模式2: 迭代器与借用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 fn main () { let names = vec! [ String ::from ("Alice" ), String ::from ("Bob" ), String ::from ("Charlie" ), ]; for name in names.iter () { println! ("Hello, {}" , name); } println! ("Still have {} names" , names.len ()); for name in names.into_iter () { println! ("Goodbye, {}" , name); } let mut scores = vec! [1 , 2 , 3 ]; for score in scores.iter_mut () { *score *= 2 ; } println! ("{:?}" , scores); }
模式3: 内部可变性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 use std::cell::RefCell;struct Logger { messages: RefCell<Vec <String >>, }impl Logger { fn new () -> Self { Logger { messages: RefCell::new (Vec ::new ()), } } fn log (&self , msg: &str ) { self .messages.borrow_mut ().push (String ::from (msg)); } fn dump (&self ) { for msg in self .messages.borrow ().iter () { println! ("{}" , msg); } } }
模式4: Cow(Clone on Write)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 use std::borrow::Cow;fn process_name (name: &str ) -> Cow<str > { if name.contains (' ' ) { Cow::Owned (name.replace (' ' , "_" )) } else { Cow::Borrowed (name) } }let name1 = process_name ("hello" ); let name2 = process_name ("hello world" );
常见编译错误和解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 let mut v = vec! [1 , 2 , 3 ];let first = &v[0 ]; v.push (4 ); println! ("{}" , first);let v = vec! [String ::from ("hello" )];let s = v[0 ]; let s = v[0 ].clone (); fn bad () -> &str { let s = String ::from ("hello" ); &s }fn good () -> String { String ::from ("hello" ) }let mut data = vec! [1 , 2 , 3 ];let closure = || data.push (4 ); data.push (5 ); closure ();
总结
Rust所有权系统的核心要点:
所有权 :每个值只有一个所有者,离开作用域时自动释放
移动语义 :赋值和传参默认移动所有权(Copy类型除外)
借用规则 :同时只能有多个不可变借用或一个可变借用
生命周期 :确保引用不会超过被引用数据的生存期
NLL :借用在最后一次使用后结束,而非词法作用域结束
实用模式 :&str优于&String、Cow避免不必要克隆、RefCell提供内部可变性
所有权系统是Rust学习曲线最陡峭的部分,但一旦理解,它就是编写安全高效代码的利器。编译器不是你的敌人,它是在编译期发现了运行时才会暴露的内存错误。