为何Rust允许同时持有同一对象的可变与不可变引用(仅使用其一)?
Rust可变与不可变引用共存的编译规则解释
先看你提到的两段代码:
编译通过的代码
fn main() { let mut str_1 = String::from("hello"); let ref_1 = &str_1; // 创建不可变引用 let ref_2 = &mut str_1; // 创建可变引用 println!("{}", ref_2); // 仅使用可变引用 }
编译失败的代码
fn main() { let mut str_1 = String::from("hello"); let ref_1 = &str_1; let ref_2 = &mut str_1; println!("{}", ref_2); println!("{}", ref_1); // 同时使用两个引用,触发编译错误 }
问题一:是否只要不使用ref_1就可编译?
是的,只要没有同时活跃使用不可变引用ref_1和可变引用ref_2,代码就能通过编译。
Rust 1.31之后引入的非词法生命周期(Non-Lexical Lifetimes, NLL)让借用检查器不再以代码块为单位判断生命周期,而是精准追踪每个引用的最后一次使用位置。在第一段代码中,ref_1创建后完全没被使用,编译器会认为它的生命周期在ref_2创建前就已经结束,不会和可变引用产生冲突,因此允许编译。
哪怕你使用了ref_1,只要它的最后一次使用早于可变引用的创建,同样能编译:
fn main() { let mut str_1 = String::from("hello"); let ref_1 = &str_1; println!("{}", ref_1); // ref_1的最后一次使用 let ref_2 = &mut str_1; // 此时ref_1已不再活跃,允许创建可变引用 println!("{}", ref_2); }
问题二:为何Rust允许这种仅使用其一的引用共存情况?
这是NLL带来的灵活性优化,核心是在保证内存安全的前提下,让代码更自然易读,减少不必要的嵌套。
在没有NLL的旧版Rust中,引用的生命周期绑定到代码块,你必须手动用代码块分隔不同引用的使用范围:
// 旧版Rust的写法,需要额外嵌套代码块 fn main() { let mut str_1 = String::from("hello"); { let ref_1 = &str_1; println!("{}", ref_1); } // 代码块结束,ref_1生命周期终止 let ref_2 = &mut str_1; println!("{}", ref_2); }
NLL让编译器能智能判断引用的实际活跃区间,不需要手动加代码块。Rust的核心规则从来不是“不能同时创建可变和不可变引用”,而是“不能同时活跃使用可变和不可变引用”——创建引用但不使用,不会触发数据竞争或内存安全问题,所以编译器允许这种写法,既守住了安全底线,又让代码编写更顺畅。
内容的提问来源于stack exchange,提问作者gANDALF
相关产品推荐
相关产品推荐

