写点什么

一种绕过 Rust 借用检查和不可变性的方法

作者:SkyFire
  • 2024-10-06
    陕西
  • 本文字数:1923 字

    阅读完需:约 6 分钟

背景

Rust 是一种系统编程语言,它通过所有权(Ownership)、借用(Borrowing)和生命周期(Lifetimes)的概念来保证内存安全,而不需要垃圾回收机制。Rust 的借用检查机制是其核心特性之一,它在编译时确保程序的内存安全。


借用检查的规则是,同一时间只能有一个可变借用:这意味着在任何给定时间,要么只有一个可变引用,要么有多个不可变引用,但不能同时拥有。


以下是一个例子:


fn main() {    let mut s = String::from("hello");    let r1 = &mut s;    let r2 = &mut s;
*r1 = "world".to_string();
println!("{}, {}", r1, r2);}
复制代码


编译时报错:


❯ cargo run   Compiling ref_test v0.1.0 (/home/skyfire/code/tmp/ref_test)error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:4:14  |3 |     let r1 = &mut s;  |              ------ first mutable borrow occurs here4 |     let r2 = &mut s;  |              ^^^^^^ second mutable borrow occurs here5 |6 |     *r1 = "world".to_string();  |     --- first borrow later used here
复制代码


fn main() {    let s = String::from("hello");    let r1 = &mut s;
*r1 = "world".to_string();
println!("{}", r1);}
复制代码


编译时报错:


❯ cargo run   Compiling ref_test v0.1.0 (/home/skyfire/code/tmp/ref_test)error[E0596]: cannot borrow `s` as mutable, as it is not declared as mutable --> src/main.rs:3:14  |3 |     let r1 = &mut s;  |              ^^^^^^ cannot borrow as mutable  |help: consider changing this to be mutable  |2 |     let mut s = String::from("hello");  |         +++
For more information about this error, try `rustc --explain E0596`.error: could not compile `ref_test` (bin "ref_test") due to 1 previous error
复制代码


但是有时候我们确实需要在保证安全的前提下存在多个可变引用指向同一个对象,或者将一个不可变引用转换为可变引用。

方法

要将一个不可变引用转换为可变引用,或者同时拥有多个可变引用,需要使用 unsafe 实现,以下是一个例子:


fn main() {    // 创建一个字符串对象    let s = String::from("hello");        // 使用不安全代码获取字符串的可变引用    #[allow(invalid_reference_casting)]    let r1: &mut String =  unsafe {&mut *(&s as *const String as *mut String)};
// 通过可变引用修改字符串的内容 *r1 = "world".to_string(); // 打印修改后的字符串 println!("{}", s);
// 再次使用不安全代码获取字符串的可变引用 #[allow(invalid_reference_casting)] let r2: &mut String = unsafe {&mut *(&s as *const String as *mut String)}; // 通过可变引用修改字符串的内容 *r2 = "世界".to_string(); // 打印修改后的字符串 println!("{}", s);}
复制代码


上面的代码中,分别使用 unsafe 获取了一个不可变字符串的两个不可变引用。打破了 Rust 的借用检查机制。


为了更容易使用,可以定义两个宏来实现:


// 定义一个宏,用于获取可变引用macro_rules! get_mut_ref {    ($e:expr, $t:ty) => {        {            #[allow(invalid_reference_casting)]            unsafe { &mut *(&$e as *const $t as *mut $t) }        }    };}
// 定义一个宏,用于将不可变引用转换为可变引用macro_rules! to_mut_ref { ($ptr:expr, $t:ty) => { { #[allow(invalid_reference_casting)] unsafe { &mut *($ptr as *const $t as *mut $t) } } };}
// 主函数fn main() { // 创建一个字符串对象 let s = String::from("hello");
// 获取字符串的可变引用,并修改其值 let r1: &mut String = get_mut_ref!(s, String); *r1 = "world".to_string(); println!("{}", s);
// 获取字符串的不可变引用,并将其转换为可变引用,再修改其值 let r2 : &String = &s; let r3 : &mut String = to_mut_ref!(r2, String); *r3 = "世界".to_string(); println!("{}", s);}
复制代码

使用建议

在使用这两个宏时,需要注意以下几点建议:


  1. 尽量避免使用:这个方法突破了 Rust 的借用检查限制和不可变的限制,应该作为其他所有方式均失效后的备选方案,避免破坏 Rust 的安全性。

  2. 类型匹配:在调用 get_mut_ref!to_mut_ref! 宏时,确保传入的表达式和类型参数是匹配的。错误的类型转换可能导致运行时错误。

  3. 文档和注释:在代码中使用这些宏时,添加适当的注释,以便其他开发者能够理解使用此方法的目的和考量。

发布于: 刚刚阅读数: 5
用户头像

SkyFire

关注

这个cpper很懒,什么都没留下 2018-10-13 加入

会一点点cpp的苦逼码农

评论

发布
暂无评论
一种绕过Rust借用检查和不可变性的方法_rust_SkyFire_InfoQ写作社区