Rust 学习(7):所有权系统(下)-引用与借用 文章目录一、回顾与导入二、引用的基本概念2.1 不可变引用Immutable Reference2.2 引用 vs 解引用三、可变引用Mutable Reference四、借用规则Borrowing Rules规则 1可变引用在同一作用域内只能有一个规则 2不可变引用可以有多个但不能与可变引用共存规则 3引用的作用域持续到最后一次使用表格总结五、悬垂引用Dangling References六、引用的类型总结七、完整代码示例八、常见陷阱与最佳实践陷阱 1误用 和 mut陷阱 2在循环中创建可变引用最佳实践九、总结与速查十、思考题参考链接返回首页 | 上篇 | 下篇掌握引用的使用 —— 借用、可变引用、悬垂引用与借用规则一、回顾与导入上篇文章我们学习了所有权的核心规则每个值有唯一所有者赋值/传参会转移所有权移动语义离开作用域自动释放内存但这带来了一个问题如果我只是想读取一个值而不想获取它的所有权难道每次都要传入再返回吗太繁琐了// 上篇的繁琐写法fnmain(){lets1String::from(hello);let(s2,len)calculate_length(s1);println!({}的长度是 {},s2,len);}fncalculate_length(s:String)-(String,usize){letlengths.len();(s,length)}Rust 提供了优雅的解决方案引用Reference和借用Borrowing。二、引用的基本概念引用允许你使用值但不获取所有权就像看一本书时可以借阅而不需要买下来。2.1 不可变引用Immutable Reference使用符号创建引用fnmain(){lets1String::from(hello);letlencalculate_length(s1);// s1 创建对 s1 的引用println!({}的长度是 {},s1,len);// s1 仍然可用}fncalculate_length(s:String)-usize{s.len()// 使用引用访问值}// s 是引用离开作用域不会 drop 原值内存模型s1 (栈) 堆 --------------------------- --------------------------------------------- | ptr | len | capacity | | h | e | l | l | o | --------------------------- --------------------------------------------- ↑ | s1 (栈) --------- | ptr | → 指向 s1 ---------关键点引用不会获取所有权原变量s1仍然拥有数据引用离开作用域不会释放数据示例letx42;// x 在栈上letrx;// r 在栈上存的是 x 的地址指向栈letbBox::new(42);// 堆上存 42栈上存 b指向堆letrb;// r 在栈上存的是 b 的地址指向栈// 间接指向堆上的 42// 更直接letvvec![1,2,3];// 数据在堆上letrv[0];// r 指向堆上的第一个元素内存示意图栈地址 内容 0x1000: x 42 0x1008: r 0x1000 (指向 x) 栈地址 内容 0x2000: b 0x5000 (Box指向堆) 0x2008: r 0x2000 (指向 b) 堆地址 内容 0x5000: 422.2 引用 vs 解引用概念符号含义创建引用value创建对值的引用解引用*ref获取引用指向的值fnmain(){letx5;letyx;// y 是引用assert_eq!(5,x);assert_eq!(5,*y);// *y 解引用取得值}三、可变引用Mutable Reference如果需要修改引用的值可以使用可变引用mut。fnmain(){letmutsString::from(hello);change(muts);// 传递可变引用println!({},s);// hello, world}fnchange(some_string:mutString){some_string.push_str(, world);}关键规则原变量必须是mut声明的引用类型必须是mut可变引用有严格的使用限制见下文四、借用规则Borrowing RulesRust 编译器对引用的使用施加了严格的规则确保内存安全。规则 1可变引用在同一作用域内只能有一个fnmain(){letmutsString::from(hello);letr1muts;letr2muts;// ❌ 编译错误不能同时存在两个可变引用println!({}, {},r1,r2);}为什么防止数据竞争Data Race。规则 2不可变引用可以有多个但不能与可变引用共存fnmain(){letmutsString::from(hello);letr1s;// 不可变引用letr2s;// 不可变引用多个没问题letr3muts;// ❌ 编译错误已有不可变引用时不能有可变引用println!({}, {},r1,r2);}因为不可变引用希望数据不被修改可变引用允许修改两者同时存在会造成冲突。规则 3引用的作用域持续到最后一次使用fnmain(){letmutsString::from(hello);letr1s;// 不可变引用开始letr2s;// 不可变引用开始println!({} {},r1,r2);// 不可变引用最后使用// r1, r2 的作用域到此结束letr3muts;// ✅ 可以因为前面的不可变引用已失效println!({},r3);}表格总结场景是否允许说明多个不可变引用✅同时读取是安全的一个可变引用✅唯一写入权可变引用 不可变引用共存❌读写冲突多个可变引用❌写入冲突五、悬垂引用Dangling References悬垂引用指向的内存已经被释放。Rust 编译器会在编译期阻止悬垂引用的产生。fnmain(){letreference_to_nothingdangle();}fndangle()-String{// ❌ 编译错误letsString::from(hello);s// 返回 s 的引用}// s 离开作用域被 drop引用指向已释放的内存错误信息error[E0106]: missing lifetime specifier -- 5 | fn dangle() - String { | ^ expected named lifetime parameter解决方案返回String本身转移所有权而不是引用fnno_dangle()-String{letsString::from(hello);s// 所有权转移出去}六、引用的类型总结引用类型语法能否修改原值能否复制限制不可变引用T❌✅引用本身可复制可以有多个可变引用mut T✅❌同一时刻只能有一个裸指针*const T/*mut T视情况✅不安全需 unsafe七、完整代码示例fnmain(){println!( 1. 不可变引用 );lets1String::from(Rust);letlenget_length(s1);println!({} 长度 {}, s1 仍然可用: {},s1,len,s1);println!(\n 2. 可变引用 );letmuts2String::from(Hello);println!(修改前: {},s2);append_world(muts2);println!(修改后: {},s2);println!(\n 3. 多个不可变引用 );lets3String::from(Multiple);letr1s3;letr2s3;letr3s3;println!(三个引用指向: {}, {}, {},r1,r2,r3);println!(\n 4. 可变引用不可共存示例 );letmuts4String::from(test);letr1s4;letr2s4;println!(r1 {}, r2 {},r1,r2);// r1 和 r2 在这之后不再使用letr3muts4;// ✅ 可以因为前面的引用不再使用r3.push_str(_mutated);println!(r3 {},r3);println!(\n 5. 解引用 );letx42;letyx;println!(x {}, y {}, *y {},x,y,*y);}fnget_length(s:String)-usize{s.len()// 通过引用访问}fnappend_world(s:mutString){s.push_str(, world!);// 通过可变引用修改}println!是宏它只是读取 s2并没有转移所有权。println! 宏会自动对参数取引用等价于println!({},s2);// 传递的是 String不是 String八、常见陷阱与最佳实践陷阱 1误用和mutfnmain(){letmutsString::from(hello);letrs;// 不可变引用// r.push_str(!); // ❌ 不可变引用不能修改letmmuts;// 可变引用m.push_str(!);}陷阱 2在循环中创建可变引用fnmain(){letmutvvec![1,2,3];// ❌ 编译错误不能同时在循环中持有可变引用自己// for item in mut v {// v.push(*item 1);// }// ✅ 正确做法先收集再修改letnew_elements:Veci32v.iter().map(|x|x1).collect();v.extend(new_elements);}最佳实践场景推荐做法只需要读取数据使用T不可变引用需要修改数据使用mut T可变引用需要同时读取多个地方使用多个T需要修改且无关顺序使用mut T注意作用域不确定用哪种优先用不可变引用九、总结与速查概念语法特性引用value不获取所有权可变引用mut value可以修改原值解引用*ref获取引用指向的值借用使用引用的行为遵循借用规则借用规则回顾可变引用在同一作用域内只能有一个可变引用和不可变引用不能共存引用必须在有效期内不能悬垂核心要点✅ 引用让函数可以安全地使用值而不获取所有权✅ 默认使用不可变引用T需要修改时使用mut T✅ 编译器严格执行借用规则防止数据竞争✅ 引用的作用域是从创建到最后一次使用十、思考题以下代码能编译通过吗为什么fnmain(){letmutsString::from(hello);letr1s;letr2s;letr3muts;println!({}, {}, {},r1,r2,r3);}以下代码的输出是什么fnmain(){letmutx10;letymutx;*y5;println!({},x);}为什么 Rust 不允许同时有可变引用和不可变引用参考链接Rust Book - References and BorrowingRust Reference - ReferencesRust by Example - Borrowing返回首页 | 上篇 | 下篇