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

如何通过裸指针在Rust闭包内修改结构体字段?

如何在Rust闭包中修改裸指针指向的AppContext字段(跨函数存储闭包场景)

我有一段从C代码调用的线程处理函数,context是堆上结构体的裸指针(通过Box::into_raw(Box::new(...))创建)。闭包会被传入add_callback函数并存储在回调向量中,后续由其他函数调用。现在想在闭包内修改app_context的字段,但直接编写会报错。

原代码

#[no_mangle]
pub unsafe extern "C" fn worker_thread_proc(context: *mut c_void) -> c_int {
    unsafe {
        let app_context = context as *mut AppContext;

        let my_closure = Rc::new(|a: N, _, _, _| {
            (*app_context).foo = 0; // 如何正确实现?
            0
        });

        // 将闭包保存到某个对象的Vec中

        ...
   }
}

触发的编译错误

error[E0597]: `app_context` does not live long enough

...
}
- `app_context` dropped here while still borrowed

我明确知道裸指针指向的对象在worker_thread_proc函数结束后仍会保持有效,但不知道如何实现修改上下文结构体状态的目标。尝试过对指针使用Rc并克隆,也出现“生命周期不足”的错误,推测可能通过Box实现,但没找到具体方法。

更新:最小可复现示例(MRE)

use std::ffi::c_void;
use std::ptr;
use std::rc::Rc;

struct AppContext {
    ctx: *mut Ctx,
    foo: u32
}

pub struct Native(pub Rc<dyn Fn(N, N, N, N) -> N>);

pub enum N {
    FuncNativeDef(Native),
    Unit
}

pub struct Ctx {
    pub values: Vec<N>
}

impl Ctx {
    pub fn new() -> Ctx {
        Ctx {
            values: Vec::new()
        }
    }

    #[inline(always)]
    pub fn set_val(&mut self, name: &str, n: N) {
        self.values.push(n);
    }
}

#[no_mangle]
pub unsafe extern "C" fn worker_thread_proc(context: *mut c_void) {
    unsafe {
        let app_context = context as *mut AppContext;
        let mut ctx = &mut *(*app_context).ctx;

        let my_closure = Rc::new(|a: N, _, _, _| {
            (*app_context).foo = 2;
            N::Unit
        });

        ctx.set_val("my_closure", N::FuncNativeDef(Native(my_closure)));
    }
}

fn main() {
    let context = Box::new(AppContext{
        foo: 42,
        ctx: Box::into_raw(Box::new(Ctx::new()))
    });

    let context_ptr = Box::into_raw(context);
}

解决方案

核心问题是Rust的生命周期检查无法自动识别裸指针指向的对象会在函数外存活,因此需要手动调整生命周期约束或采用符合Rust安全规范的所有权管理方式,同时保证操作的安全性(需自行确认指针长期有效)。

方法1:用Rc+内部可变性(推荐)

这种方式符合Rust的安全模型,能清晰管理对象的生命周期和可变性。如果涉及多线程,将Rc替换为Arc,内部可变性用Mutex/RwLock即可。

修改步骤:

  1. 调整AppContext结构,用Cell包装可修改字段(Cell适用于Copy类型,非Copy类型用RefCell):
use std::cell::Cell;

struct AppContext {
    ctx: *mut Ctx,
    foo: Cell<u32> // 用Cell实现内部可变性
}
  1. 创建AppContext时直接用Rc包装,并传递克隆后的裸指针给C:
fn main() {
    let context = Rc::new(AppContext{
        foo: Cell::new(42),
        ctx: Box::into_raw(Box::new(Ctx::new()))
    });
    // 转成裸指针传给C前先克隆Rc,保证原引用计数正确
    let context_ptr = Rc::into_raw(context.clone()) as *mut c_void;
}
  1. 在线程处理函数中恢复Rc,并让闭包捕获克隆的Rc:
#[no_mangle]
pub unsafe extern "C" fn worker_thread_proc(context: *mut c_void) {
    // 从裸指针恢复Rc
    let app_context = Rc::from_raw(context as *mut AppContext);
    let ctx = &mut *(*app_context).ctx;

    // 克隆Rc供闭包捕获
    let app_ctx_clone = app_context.clone();
    let my_closure = Rc::new(move |a: N, _, _, _| {
        // 通过Cell修改字段
        app_ctx_clone.foo.set(2);
        N::Unit
    });

    ctx.set_val("my_closure", N::FuncNativeDef(Native(my_closure)));
    // 不要主动drop app_context,C端仍持有引用,后续不再使用时再用Rc::from_raw处理回收
}

方法2:强制转换为'static生命周期(不推荐,仅用于特殊场景)

如果不想改动AppContext结构,可以强制将指针的生命周期转为'static来绕过编译器检查,但这种操作完全依赖你自己保证指针的有效性,一旦对象提前释放会导致未定义行为:

#[no_mangle]
pub unsafe extern "C" fn worker_thread_proc(context: *mut c_void) {
    unsafe {
        let app_context = context as *mut AppContext;
        // 强制将指针生命周期转为'static
        let app_context_static = app_context as *mut AppContext;
        let ctx = &mut *(*app_context).ctx;

        let my_closure = Rc::new(move |a: N, _, _, _| {
            unsafe {
                (*app_context_static).foo = 2;
            }
            N::Unit
        });

        ctx.set_val("my_closure", N::FuncNativeDef(Native(my_closure)));
    }
}

方法3:用Box转移所有权

如果可以确保闭包是AppContext的唯一持有者(后续C代码不会再使用该指针),可以将裸指针转回Box,让闭包捕获Box<AppContext>:

#[no_mangle]
pub unsafe extern "C" fn worker_thread_proc(context: *mut c_void) {
    // 从裸指针恢复Box,转移所有权
    let mut app_context = Box::from_raw(context as *mut AppContext);
    let ctx = &mut *app_context.ctx;

    let my_closure = Rc::new(move |a: N, _, _, _| {
        app_context.foo = 2;
        N::Unit
    });

    ctx.set_val("my_closure", N::FuncNativeDef(Native(my_closure)));
    // 不要主动drop app_context,所有权已转移到闭包中
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 15:26:04