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

Rust中结构体方法调用的可变/不可变借用冲突解决

问题描述

我定义了一个持有HelperStruct实例的MainStruct,希望实现一个方法调用Helper的方法,但Helper的方法需要对MainStruct进行不可变访问。代码实现如下:

struct MainStruct {
    helper: HelperStruct,
}

impl MainStruct {
    pub fn call_helper(&mut self, args) { 
        // &mut self 是因为the_method会修改HelperStruct的内部状态
        self.helper.the_method(self, args)
    }
}

impl HelperStruct {
    pub fn the_method(&mut self, owner: &MainStruct, args) {
        owner.other_method();
    }
}

运行时出现以下错误:

error[E0502]: cannot borrow `self.helper` as mutable because it is also borrowed as immutable
214 |         self.helper.the_method(self, args)
    |         ^^^^^^^^^^^^----------^----^^^^^^^^^^^^^^^^^^^^^^
    |         |           |            |
    |         |           |            immutable borrow occurs here
    |         |           immutable borrow later used by call
    |         mutable borrow occurs here

实际场景中,MainStruct对应游戏中的Field,HelperStruct是负责A寻路的结构体——因频繁调用A需复用其内部数组,仅在每次寻路后重置部分状态;而A*寻路需要访问Field的信息,同时自身状态会被修改,因此the_method实际为:

pub fn full_pathing(&mut self, field: &Field, source: (i32, i32), destination: (i32, i32))

要求:MainStruct必须作为功能交互的主接口(因此不能让Helper持有Main),且the_method仅会在MainStruct实例内部调用。请问如何重构代码或使用智能指针来解决该问题?

解决方案

方法1:拆分借用,分离可变与不可变数据

这是最符合Rust安全规则的方案,核心思路是把MainStruct中需要被Helper访问的不可变数据单独抽离,避免同时借用整个self和self.helper。

struct MainStruct {
    helper: HelperStruct,
    // Field的核心数据,供寻路访问
    terrain: Vec<Vec<u8>>,
    width: i32,
    height: i32,
}

impl MainStruct {
    // 生成仅包含不可变核心数据的视图
    pub fn field_view(&self) -> FieldView {
        FieldView {
            terrain: &self.terrain,
            width: self.width,
            height: self.height,
        }
    }

    pub fn call_helper(&mut self, source: (i32, i32), destination: (i32, i32)) {
        // 先获取不可变的Field视图,此时未借用helper
        let field_view = self.field_view();
        // 再对helper进行可变借用,无冲突
        self.helper.full_pathing(&field_view, source, destination);
    }

    pub fn other_method(&self) {
        // 原有逻辑实现
    }
}

// 仅承载Field不可变数据的视图结构体
struct FieldView<'a> {
    terrain: &'a Vec<Vec<u8>>,
    width: i32,
    height: i32,
}

impl HelperStruct {
    pub fn full_pathing(&mut self, field: &FieldView, source: (i32, i32), destination: (i32, i32)) {
        // 访问Field的不可变数据
        println!("寻路范围:{}x{},起点{:?},终点{:?}", field.width, field.height, source, destination);
        // 修改自身复用的内部状态
        self.reset_internal_arrays();
    }

    fn reset_internal_arrays(&mut self) {
        self.open_list.clear();
        self.closed_list.clear();
    }
}

struct HelperStruct {
    // 复用的A*寻路数组
    open_list: Vec<(i32, i32)>,
    closed_list: Vec<(i32, i32)>,
}

方法2:使用内部可变性RefCell

如果拆分数据结构成本高,可以用RefCell包装Helper的状态,把编译期的借用检查移到运行期,这样MainStruct的方法只需传不可变引用即可。

use std::cell::RefCell;

struct MainStruct {
    helper: RefCell<HelperStruct>,
    terrain: Vec<Vec<u8>>,
    width: i32,
    height: i32,
}

impl MainStruct {
    pub fn call_helper(&self, source: (i32, i32), destination: (i32, i32)) {
        // 通过RefCell获取Helper的可变引用
        let mut helper = self.helper.borrow_mut();
        helper.full_pathing(self, source, destination);
    }

    pub fn other_method(&self) {
        // 原有逻辑实现
    }
}

impl HelperStruct {
    pub fn full_pathing(&mut self, field: &MainStruct, source: (i32, i32), destination: (i32, i32)) {
        // 访问Field的不可变数据
        println!("基于地形数据寻路:{}x{}", field.width, field.height);
        // 修改自身复用状态
        self.open_list.clear();
        self.closed_list.clear();
    }
}

struct HelperStruct {
    open_list: Vec<(i32, i32)>,
    closed_list: Vec<(i32, i32)>,
}

注意:RefCell仅适合单线程场景(游戏逻辑通常是单线程),如果出现并发可变借用会直接panic。

方法3:unsafe代码(不推荐)

作为最后手段,可以用unsafe绕过借用检查,但会失去Rust的内存安全保障,仅在你能100%确保无数据竞争或悬垂引用时使用:

struct MainStruct {
    helper: HelperStruct,
    terrain: Vec<Vec<u8>>,
    width: i32,
    height: i32,
}

impl MainStruct {
    pub fn call_helper(&mut self, source: (i32, i32), destination: (i32, i32)) {
        // 将&mut self转为不可变指针再转回引用
        let field_ptr = self as *const MainStruct;
        let field = unsafe { &*field_ptr };
        // 此时调用helper的可变方法不会触发借用冲突
        self.helper.full_pathing(field, source, destination);
    }
}

// 其余代码与初始结构一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 21:50:54