You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中被不可变借用的结构体无法修改的原因及解决方案

问题解析与解决方案

一、为什么Rust编译器禁止这种操作?

Rust的核心保障是所有权与借用规则,其中最关键的一条是:同一时间,一个值只能存在多个不可变引用,或者一个可变引用,二者绝对不能同时存在。

在你的代码里,alice和bob已经持有了car的不可变引用(&Vehicle),此时再尝试对car执行可变操作(修改name和horsepowers),直接违反了这条规则——相当于同时存在不可变引用和可变引用,编译器会直接拒绝编译,从根源上阻断风险。

二、这种操作会引发哪些内存安全问题?

如果编译器允许这种操作,会直接导致两类严重的内存安全问题:

  • 数据竞争(Data Race):当多个不可变引用在读取数据的同时,有一个可变引用在修改数据,无论单线程还是多线程场景,都可能读取到处于“半修改状态”的无效数据,导致程序行为完全不可预测。比如Alice读取car.name的过程中,car.name被修改,可能读到旧字符串片段和新字符串片段混合的无效值。
  • 悬垂引用风险:对于String这类堆分配类型,修改内容时可能触发内存重新分配(比如字符串长度超过原有缓冲区容量),此时原有的不可变引用alice.vehicle.name会指向已经被系统回收的内存,变成悬垂引用,后续访问会直接触发内存错误(比如段错误)。

三、如何实现“多人共享车辆同时修改车辆”的需求?

可以通过Rust的**内部可变性(Interior Mutability)**机制解决,常用工具是RefCell<T>(单线程场景)或Rc<RefCell<T>>(需要多所有权共享时)。

方案1:用RefCell<T>实现内部可变性

RefCell<T>允许在持有不可变引用的前提下,通过运行时检查来获取可变引用——编译期不再严格限制,但运行时会强制遵守借用规则(违反则直接panic)。

修改后的代码示例:

use std::cell::RefCell;

enum VehicleType {
    Car,
    Motorcycle,
}

struct Vehicle {
    name: String,
    horsepowers: i32,
    vehicle_type: VehicleType,
}

struct Person<'a> {
    vehicle: &'a RefCell<Vehicle>,
    name: &'a str,
}

fn main() {
    let car = RefCell::new(Vehicle {
        name: "Nissan GTR".to_string(),
        horsepowers: 300,
        vehicle_type: VehicleType::Car,
    });

    let alice = Person {
        name: "Alice",
        vehicle: &car,
    };

    let bob = Person {
        name: "Bob",
        vehicle: &car,
    };

    println!("{} drives {}", alice.name, alice.vehicle.borrow().name);
    println!("{} drives {}", bob.name, bob.vehicle.borrow().name);

    // 修改车辆信息
    let mut mut_car = car.borrow_mut();
    mut_car.name = "Lamborghini".to_string();
    mut_car.horsepowers = 684;
    drop(mut_car); // 可选:手动释放可变借用,确保后续不可变借用能顺利获取

    println!("{} drives {}", alice.name, alice.vehicle.borrow().name);
    println!("{} drives {}", bob.name, bob.vehicle.borrow().name);
}

方案2:用Rc<RefCell<T>>实现多所有权共享

如果需要让Person结构体拥有车辆的所有权(而非单纯借用),可以结合Rc(引用计数智能指针)和RefCell,实现多个Person共享同一车辆的所有权,同时支持修改:

use std::cell::RefCell;
use std::rc::Rc;

enum VehicleType {
    Car,
    Motorcycle,
}

struct Vehicle {
    name: String,
    horsepowers: i32,
    vehicle_type: VehicleType,
}

struct Person {
    vehicle: Rc<RefCell<Vehicle>>,
    name: &'static str,
}

fn main() {
    let car = Rc::new(RefCell::new(Vehicle {
        name: "Nissan GTR".to_string(),
        horsepowers: 300,
        vehicle_type: VehicleType::Car,
    }));

    let alice = Person {
        name: "Alice",
        vehicle: Rc::clone(&car),
    };

    let bob = Person {
        name: "Bob",
        vehicle: Rc::clone(&car),
    };

    println!("{} drives {}", alice.name, alice.vehicle.borrow().name);
    println!("{} drives {}", bob.name, bob.vehicle.borrow().name);

    // 修改车辆信息
    let mut mut_car = car.borrow_mut();
    mut_car.name = "Lamborghini".to_string();
    mut_car.horsepowers = 684;

    println!("{} drives {}", alice.name, alice.vehicle.borrow().name);
    println!("{} drives {}", bob.name, bob.vehicle.borrow().name);
}

注意事项

  • RefCell<T>仅适用于单线程场景,如果是多线程环境,需要替换为Mutex<T>或RwLock<T>,并结合Arc(原子引用计数智能指针)使用。
  • 使用borrow()和borrow_mut()时,运行时会严格检查借用规则,如果出现同时持有可变和不可变引用的情况,程序会直接panic,因此需要确保代码逻辑不会触发这类冲突。

内容的提问来源于stack exchange,提问作者Davo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 12:45:43