移植含可变借用的C代码时遇运行时错误(nappgui示例)
问题描述
我有系统编程领域的C/C++开发背景,目前正在转向Rust语言。完成Rust官方手册与Rustlings课程后,我决定移植nappgui库来巩固对Rust的理解。
nappgui中频繁使用一种涉及可变借用的模式,转译为Rust代码后虽能编译,但触发了可变借用相关的运行时错误。以下是最小复现代码:
////////////////////////////////////////////////////////// // Platform specific OSAPP library crate // e.g. osapp_win.rs pub struct OSApp { abnormal_termination: bool, with_run_loop: bool, } pub fn init_imp(with_run_loop: bool) -> Box<OSApp> { Box::new(OSApp { abnormal_termination: false, with_run_loop: with_run_loop, }) } pub fn run(app: &OSApp, on_finish_launching: &mut dyn FnMut()) { on_finish_launching(); if app.with_run_loop { // Following line commented out to simplify // osgui::message_loop(); i_terminate(app); } } fn i_terminate(_app: &OSApp) { // Calls more client callbacks } ////////////////////////////////////////////////////////// // OSAPP crate use core::f64; use std::{cell::RefCell, rc::Rc}; struct App { osapp: Option<Box<OSApp>>, _lframe: f64, func_create: FnAppCreate, } pub trait ClientObject {} type FnAppCreate = fn() -> Box<dyn ClientObject>; pub fn osmain(lframe: f64, func_create: FnAppCreate) { let app = Rc::new(RefCell::new(App { osapp: None, _lframe: lframe, func_create: func_create, })); let osapp: Box<OSApp> = osapp_init(true); let tmp_a = app.clone(); tmp_a.as_ref().borrow_mut().osapp = Some(osapp); let tmp_b = app.clone(); let on_finish_launch = || { // I understand why I get the already borrowed mutable error here i_OnFinishLaunching(&tmp_b.as_ref().borrow()); // ^^^^^^^^^^^^^^^^^^^^^^^^^ }; let tmp_c = &app.as_ref().borrow_mut().osapp; if let Some(osapp) = tmp_c { /*osapp::*/ run(&osapp, &mut &on_finish_launch); } } fn osapp_init(with_run_loop: bool) -> Box<OSApp> { /*osapp::*/ init_imp(with_run_loop) } fn i_OnFinishLaunching(app: &App) { (app.func_create)(); } ////////////////////////////////////////////////////////// // main.rs struct Application { // widgets go here } impl ClientObject for Application {} impl Application { fn create() -> Box<dyn ClientObject> { let mut app = Box::new(Application { // Create all the widgets here }); app } } fn main() { /*osapp::*/ osmain(0.0, Application::create); }
运行输出:
thread 'main' panicked at src/main.rs:55:45: already mutably borrowed: BorrowError note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
我理解错误原因,希望获得重构代码以避免该错误的指导,尤其欢迎针对C++可变借用模式转译为Rust的相关见解。
补充说明:上述代码对应项目中广泛使用的C语言架构模式:
#include <stdio.h> #include <stdlib.h> typedef void(*CB1_T)(void*) ; typedef void(*CB2_T)(void*) ; struct LowLevelObject { void *listener; // state data CB1_T callback1; CB2_T callback2; }; void low_api1(struct LowLevelObject *lo) { // some functionality lo->callback1(lo->listener); // more functionality } void low_api2(struct LowLevelObject *lo) { // some functionality lo->callback2(lo->listener); // more functionality } void low_api3(struct LowLevelObject *lo) { // some functionality printf("%s\n", __func__); } struct LowLevelObject *low_create( void *listener, CB1_T callback1, CB2_T callback2) { struct LowLevelObject *lo = calloc(1, sizeof(struct LowLevelObject)); lo->listener = listener; lo->callback1 = callback1; lo->callback2 = callback2; return lo; } void low_destroy(struct LowLevelObject *lo) { free(lo); } ///////////////////////////////////////// struct HighLevelObject { struct LowLevelObject *low; // State data }; static void on_callback1(struct HighLevelObject *hi) { printf("%s\n", __func__); low_api3(hi->low); } static void on_callback2(struct HighLevelObject *hi) { printf("%s\n", __func__); low_api3(hi->low); } struct HighLevelObject *high_create() { struct HighLevelObject *hi = calloc(1, sizeof(struct HighLevelObject)); hi->low = low_create(hi, (CB1_T)on_callback1, (CB2_T)on_callback2); // NULL checks ignored for simplicity return hi; } void high_destroy(struct HighLevelObject *hi) { low_destroy(hi->low); free(hi); } void hi_start() { struct HighLevelObject *hi = high_create(); low_api1(hi->low); low_api2(hi->low); high_destroy(hi); } //////////////////////////////// int main() { hi_start(); return 0; }
解决方案
1. 核心问题分析
错误出在osmain函数中:
let tmp_c = &app.as_ref().borrow_mut().osapp;对App发起了可变借用,这个借用的生命周期会持续到if let块结束- 当
run调用on_finish_launch闭包时,闭包内尝试对App发起不可变借用,此时可变借用尚未释放,触发Rust的借用规则冲突,导致运行时BorrowError
2. 重构代码
调整osmain函数,提前获取OSApp的引用并缩短可变借用的生命周期:
pub fn osmain(lframe: f64, func_create: FnAppCreate) { let app = Rc::new(RefCell::new(App { osapp: None, _lframe: lframe, func_create: func_create, })); let osapp: Box<OSApp> = osapp_init(true); // 用代码块限制可变借用的生命周期,存入osapp后立即释放 { let mut app_mut = app.borrow_mut(); app_mut.osapp = Some(osapp); } let tmp_b = app.clone(); let mut on_finish_launch = move || { // 此时App的可变借用已释放,可安全获取不可变借用 let app_ref = tmp_b.borrow(); i_OnFinishLaunching(&app_ref); }; // 获取OSApp的不可变引用,此时无活跃的可变借用 let app_ref = app.borrow(); if let Some(osapp) = &app_ref.osapp { run(osapp, &mut on_finish_launch); } }
3. C/C++模式转译到Rust的关键思路
C/C++中常用的"对象持有自身指针给回调"模式,在Rust中需要调整思路适配借用规则:
- 缩短可变借用生命周期:用代码块
{}手动控制可变借用的作用域,避免长时间持有 - 拆分数据依赖:如果回调仅需要对象的部分数据,将这部分数据单独封装,减少对整个对象的借用需求
- 合理使用内部可变性:
Rc<RefCell<T>>是模拟C++可变指针的常用方式,但要避免嵌套借用,回调内尽量只做读取操作 - 优先考虑所有权转移:若对象无需在回调后复用,直接转移所有权,彻底避免借用冲突
4. 进一步优化建议
针对项目中大量存在的回调场景,可做如下优化:
- 初始化时直接绑定
App与OSApp的关系,避免后续的可变操作 - 用
Weak<RefCell<T>>传递给回调,规避潜在的循环引用风险 - 封装回调注册逻辑,统一管理对象的借用状态,确保回调执行时的安全性
内容的提问来源于stack exchange,提问作者Dushara
相关产品推荐
相关产品推荐

