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
相关产品推荐
相关产品推荐

