如何优化Rust调用C绑定代码实现DRY?类型别名报错问题
问题描述
我用bindgen从C头文件生成了Rust绑定,用来从字节数组读取不同数据。每个属性都要先创建对应的handle再初始化,重复代码又长又乱。
示例重复代码:
let mut handle_a: SDHandleA = std::ptr::null_mut(); unsafe { SDCreateHandleA(&mut handle_a); SDGetPropertyHandleA(base_handle, SD_PROPERTY_NAME_HANDLE_A, handle_a); }
handle_b、handle_c、handle_d的代码结构完全一样,只是名称不同。
所有SDHandle..都是*mut ::std::os::raw::c_void的类型别名:
pub type SDHandle = *mut ::std::os::raw::c_void; pub type SDHandleA = SDHandle; pub type SDHandleB = SDHandle; pub type SDHandleC = SDHandle; pub type SDHandleD = SDHandle;
我尝试用高阶函数写通用创建方法来复用代码:
fn create_handle<F, G, H>( target_type: F, create_step: G, init_step: H, base_handle: c_void, property: *mut i8 ) -> F where F: SDHandleA, G: Fn(F), H: Fn(c_void, *mut i8, F) { // 实现代码 }
调用方式:
let mut handle_a: SDHandleA = create_handle(SDHandleA, SDCreateHandleA, SDGetPropertyHandleA, base_handle, SD_PROPERTY_NAME_HANDLE_A);
但Rust报错:
error[E0404]: expected trait, found type alias `SDHandleA` --> src/lib.rs:379:12 | 123 | F: SDHandleA, | ^^^^^^^^^ type aliases cannot be used as traits
我知道SDHandleA不是trait,想问怎么实现这类代码的复用?
解决方案
因为所有SDHandleX本质都是同一种底层类型(*mut c_void),我们不需要为每个别名单独做泛型约束,而是可以通过以下几种方式实现代码复用:
方法1:直接针对底层类型编写通用函数
既然所有handle都是*mut c_void,可以把函数参数和返回值都用这个底层类型,调用时再指定具体的类型别名:
use std::os::raw::{c_void, c_char}; unsafe fn create_handle( create_fn: impl FnOnce(*mut *mut c_void), init_fn: impl FnOnce(*mut c_void, *const c_char, *mut c_void), base_handle: *mut c_void, property_name: *const c_char, ) -> *mut c_void { let mut handle = std::ptr::null_mut(); create_fn(&mut handle); init_fn(base_handle, property_name, handle); handle }
调用时直接标注需要的类型别名即可:
unsafe { let handle_a: SDHandleA = create_handle( SDCreateHandleA, SDGetPropertyHandleA, base_handle, SD_PROPERTY_NAME_HANDLE_A, ); let handle_b: SDHandleB = create_handle( SDCreateHandleB, SDGetPropertyHandleB, base_handle, SD_PROPERTY_NAME_HANDLE_B, ); }
方法2:用标记类型包装handle(编译期安全)
如果想在编译期区分不同的handle类型,避免混用,可以用零大小类型(ZST)作为标记,包装底层指针:
use std::os::raw::{c_void, c_char}; use std::marker::PhantomData; // 定义不同的标记类型,用于区分handle #[derive(Debug, Clone, Copy)] struct HandleAMarker; #[derive(Debug, Clone, Copy)] struct HandleBMarker; // 透明包装的类型化handle,保留底层指针的内存布局 #[repr(transparent)] struct TypedHandle<M>(*mut c_void, PhantomData<M>); impl<M> TypedHandle<M> { // 提供转换为底层指针的方法 pub fn as_ptr(&self) -> *mut c_void { self.0 } } // 通用创建函数 unsafe fn create_typed_handle<M>( create_fn: impl FnOnce(*mut *mut c_void), init_fn: impl FnOnce(*mut c_void, *const c_char, *mut c_void), base_handle: *mut c_void, property_name: *const c_char, ) -> TypedHandle<M> { let mut handle = std::ptr::null_mut(); create_fn(&mut handle); init_fn(base_handle, property_name, handle); TypedHandle(handle, PhantomData) }
调用方式:
unsafe { let handle_a = create_typed_handle::<HandleAMarker>( SDCreateHandleA, SDGetPropertyHandleA, base_handle, SD_PROPERTY_NAME_HANDLE_A, ); let handle_b = create_typed_handle::<HandleBMarker>( SDCreateHandleB, SDGetPropertyHandleB, base_handle, SD_PROPERTY_NAME_HANDLE_B, ); }
这种方法能在编译期防止不同类型的handle被误用,比单纯的类型别名更安全。
方法3:修正原高阶函数的泛型约束
如果坚持想用原有的类型别名,只需要去掉错误的泛型约束,利用类型别名的底层一致性,让Rust自动推导:
use std::os::raw::{c_void, c_char}; unsafe fn create_handle<F>( create_fn: impl FnOnce(*mut F), init_fn: impl FnOnce(*mut c_void, *const c_char, F), base_handle: *mut c_void, property_name: *const c_char, ) -> F where F: Copy + Into<*mut c_void> + From<*mut c_void>, { let mut handle = std::ptr::null_mut().into(); create_fn(&mut handle); init_fn(base_handle, property_name, handle); handle }
调用时无需传递SDHandleA作为参数,直接标注返回类型即可:
unsafe { let handle_a: SDHandleA = create_handle( SDCreateHandleA, SDGetPropertyHandleA, base_handle, SD_PROPERTY_NAME_HANDLE_A, ); }
内容的提问来源于stack exchange,提问作者Sven Döring
相关产品推荐
相关产品推荐

