引用进阶
> 学习悬垂引用的预防、解引用操作符的用法,以及函数参数中 &str 与 &String 的最佳实践选择。
悬垂引用(Dangling Reference)
什么是悬垂引用?
悬垂引用 = 指向已释放内存的引用
危险:
1. 访问无效内存
2. 程序崩溃
3. 安全漏洞Rust 如何防止
rust
▶ Run// ❌ 错误:返回悬垂引用
// fn create_dangle() -> &String {
// let s = String::from("hello");
// &s // s 在函数结束时被释放
// }错误信息:
error[E0515]: cannot return reference to local variable `s`
--> src/main.rs:4:5
|
4 | &s
| ^^ returns a reference to data owned by the current function正确的做法
rust
▶ Run// 方案 1:返回所有权
fn create_string() -> String {
String::from("hello")
}
fn main() {
let s = create_string();
println!("{}", s);
}
// 方案 2:使用静态生命周期
fn get_static() -> &'static str {
"hello" // 字符串字面量,整个程序生命周期
}
fn main() {
let s = get_static();
println!("{}", s);
}解引用操作符(*)
基本用法
rust
▶ Runfn main() {
let x = 5;
let y = &x; // y 是引用
assert_eq!(5, x);
assert_eq!(5, *y); // *y 解引用,获取值
}内存图解
栈:
┌──────┐
│ x=5 │
└──────┘
↑
│
┌──────┐
│ y │ → 指向 x
└──────┘
*y = 5(跟随指针获取值)自动解引用(Deref Coercion)
rust
▶ Runfn main() {
let mut s = String::from("hello");
let r = &mut s;
// Rust 会自动解引用
r.push_str(", world");
// 等价于显式解引用
(*r).push_str("!");
println!("{}", s);
}何时需要显式解引用
rust
▶ Runfn main() {
let x = 5;
let y = &x;
// 需要显式解引用的情况
let z = *y + 1; // z = 6
// 不需要解引用的情况
println!("{}", y); // Rust 自动解引用
assert_eq!(5, y); // Rust 自动解引用
}&str vs &String
最佳实践:使用 &str
rust
▶ Run// ❌ 不推荐:只接受 &String
fn greet_string(s: &String) {
println!("Hello, {}!", s);
}
// ✅ 推荐:接受 &str
fn greet_str(s: &str) {
println!("Hello, {}!", s);
}
fn main() {
let owned = String::from("Alice");
let borrowed = "Bob";
// &String 函数只能接受 String 的引用
greet_string(&owned);
// greet_string(borrowed); // ❌ 错误
// &str 函数可以接受多种类型
greet_str(&owned); // &String → &str(自动转换)
greet_str(borrowed); // &'static str
greet_str(&owned[0..3]); // 切片也是&str
}为什么 &str 更灵活
类型转换关系:
String ──────→ &str (通过 Deref)
↓ ↑
│ │
│ │
&String ─────→ &str (通过 Deref coercion)
结论:&str 是"通用接口",可以接受:
• 字符串字面量
• String 的引用
• 字符串切片小结
- Rust 编译器阻止悬垂引用,返回局部变量引用会编译失败
*解引用操作符获取引用指向的值,多数情况自动解引用- 函数参数优先使用
&str而非&String,更通用灵活 - Deref coercion 让
&String自动转换为&str
练习题
详见:练习题