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

Rust实现C语句struct->ptr=(char*)data等价写法时触发unwrap panic

问题描述

C语言中有如下结构体成员赋值逻辑:

my_struct->ptr = (char*) data;

其中my_struct结构体包含char*类型的成员ptr。
最初实现的Rust对应结构体定义如下:

struct MyStruct {
    ptr: *mut libc::c_char
}

尝试通过以下代码为该成员赋值并访问:

unsafe{*my_struct}.ptr = Box::into_raw(my_data) as *mut libc::c_char;
unsafe{((*my_struct).ptr as *mut MyData).as_mut()}.unwrap();

执行第二行访问代码时触发unwrap panic,已排除数据生命周期过期、离开作用域的问题。该场景中MyStruct的原始定义来自MySQL 8.0源码的UDF注册类型头文件。
最小复现代码如下:

#[derive(Clone, Copy)]
struct MyStruct {
    ptr: *mut libc::c_char
}

struct MyData{}

fn main() {
    let my_struct = &mut MyStruct{
        ptr: std::ptr::null_mut()
    } as *mut MyStruct;
    let my_data = Box::new(MyData{});
    unsafe{*my_struct}.ptr = Box::into_raw(my_data) as *mut libc::c_char;
    unsafe{((*my_struct).ptr as *mut MyData).as_mut()}.unwrap();
}
Panic 根本原因

问题出在赋值语句的写法和MyStruct的Copy特性共同导致的隐式拷贝:

  • 赋值语句unsafe{*my_struct}.ptr = xxx中,unsafe块仅包裹了裸指针解引用操作*my_struct,由于MyStruct被标记为Copy,这个解引用操作会拷贝生成一份MyStruct的临时副本,后续对ptr字段的赋值仅作用在这个临时副本上,语句结束后临时副本立即被丢弃,裸指针my_struct指向的原始内存根本没有被修改,ptr字段始终是初始值std::ptr::null_mut()。
  • 第二行访问代码读取到的ptr还是空指针,对空指针调用as_mut()会返回None,最终unwrap()触发panic。

本质是Rust的place expression规则限制:赋值操作的左值必须是指向真实内存位置的place,如果左值是临时生成的Copy副本,赋值只会修改副本,不会同步到原始内存位置。

正确实现方式

要实现和C语言完全等价的赋值逻辑,必须保证字段的读写操作直接作用在裸指针指向的原始内存上,避免产生隐式的Copy副本,参考实现如下:

#[derive(Clone, Copy)]
struct MyStruct {
    ptr: *mut libc::c_char
}

struct MyData{}

fn main() {
    let my_struct = &mut MyStruct{
        ptr: std::ptr::null_mut()
    } as *mut MyStruct;
    let my_data = Box::new(MyData{});
    
    unsafe {
        // 整个字段访问+赋值逻辑都放在同一个unsafe块内,直接操作原始内存
        (*my_struct).ptr = Box::into_raw(my_data) as *mut libc::c_char;
        // 访问指针指向的数据也直接基于原始内存操作
        let data_ref = ((*my_struct).ptr as *mut MyData).as_mut().unwrap();
        // 后续正常使用data_ref即可
    }
}

额外注意事项

  • 操作裸指针时,不要把解引用逻辑单独包裹在unsafe块中作为返回值,尤其当目标类型实现了Copy时,极易触发隐式拷贝,导致读写操作作用在临时值上。
  • 如果该指针用于跨FFI传递任意类型的用户私有数据,可以将ptr字段定义为*mut libc::c_void,减少不必要的指针强转,降低类型不匹配风险。
  • 通过Box::into_raw泄漏到FFI侧的内存,需要在确定不再使用时通过Box::from_raw转回Rust侧的Box主动回收,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 17:57:15