Unsafe Rust 编码技巧 | 01
编辑:张汉东
汇总一些 Unsafe Rust 编码技巧,欢迎补充。
编写 Unsafe Rust 的标准规范
来源:wasmtime
#![allow(unused)] fn main() { /// Invokes this WebAssembly function with the specified parameters. /// /// Returns either the results of the call, or a [`Trap`] if one happened. /// /// For more information, see the [`Func::typed`] and [`Func::call`] /// documentation. /// /// # Panics /// /// This function will panic if it is called when the underlying [`Func`] is /// connected to an asynchronous store. pub fn call(&self, params: Params) -> Result<Results, Trap> { assert!( !cfg!(feature = "async") || !self.func.store().async_support(), "must use `call_async` with async stores" ); unsafe { self._call(params) } } }
当函数中调用了 Unsafe 函数,必须对其进行安全抽象。
上面代码示例中,使用 assert!
宏,将 _call
调用控制在了安全边界内,所以函数 call
目前是一个安全的函数,所以不需要在 fn
前面增加 unsafe
标签。
#![allow(unused)] fn main() { unsafe fn _call(&self, params: Params) -> Result<Results, Trap> { // Validate that all runtime values flowing into this store indeed // belong within this store, otherwise it would be unsafe for store // values to cross each other. if !params.compatible_with_store(&self.func.instance.store) { return Err(Trap::new( "attempt to pass cross-`Store` value to Wasm as function argument", )); } // ... // ignore others codes // ... // This can happen if we early-trap due to interrupts or other // pre-flight checks, so we need to be sure the parameters are at least // dropped at some point. if !called { drop(params.assume_init()); } debug_assert_eq!(result.is_ok(), returned); result?; Ok(ret.assume_init()) } }
对于 _call
函数来说,因为无法在函数内验证所有传入的运行时值是否在合法的安全边界,所以需要将其标记为 Unsafe 函数,即在 fn
前面加上 unsafe
标签。除此之外,还必须在函数内脆弱的地方,加上必须的注释来说明什么情况下会突破安全边界。
#![allow(unused)] fn main() { /// A trait implemented for types which can be arguments and results for /// closures passed to [`Func::wrap`] as well as parameters to [`Func::typed`]. /// /// This trait should not be implemented by user types. This trait may change at /// any time internally. The types which implement this trait, however, are /// stable over time. /// /// For more information see [`Func::wrap`] and [`Func::typed`] pub unsafe trait WasmTy { #[doc(hidden)] type Abi: Copy; #[doc(hidden)] #[inline] fn typecheck(ty: crate::ValType) -> Result<()> { if ty == Self::valtype() { Ok(()) } else { bail!("expected {} found {}", Self::valtype(), ty) } } #[doc(hidden)] fn valtype() -> ValType; #[doc(hidden)] fn compatible_with_store(&self, store: &Store) -> bool; #[doc(hidden)] fn into_abi(self, store: &Store) -> Self::Abi; #[doc(hidden)] unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self; } }
对于上面的 trait ,因为是内部使用,随时可能发生改变。所以标记为 Unsafe ,并加上注释提示该 trait 不该又库用户自己实现,而是由维护者在内部为指定类型实现,这些类型应该是稳定的。如果用户想自己实现,那么要明白它是 Unsafe 的。
所以,不一定是出于内存安全才指定 Unsafe ,也可以作为一种和库用户的约定。
在 FFi 时方便调用 Rust 闭包
use std::os::raw::c_void; pub type Callback = unsafe extern "C" fn(user_data: *mut c_void, arg: i32) -> i32; pub unsafe extern "C" fn execute_a_closure(arg: i32, cb: Callback, user_data: *mut c_void) -> i32 { cb(user_data, arg) } /// 获取一个可以用作[`Callback`]的函数指针,该函数指针将指向闭包的指针作为其`user_data`。 pub fn raw_callback<F>(_closure: &F) -> Callback where F: FnMut(i32) -> i32, { unsafe extern "C" fn wrapper<P>(user_data: *mut c_void, arg: i32) -> i32 where P: FnMut(i32) -> i32, { let cb = &mut *(user_data as *mut P); cb(arg) } wrapper::<F> } fn main() { let mut calls = 0; let mut closure = |arg: i32| { calls += 1; arg }; unsafe { let func = raw_callback(&closure); let got = execute_a_closure(42, func, &mut closure as *mut _ as *mut c_void); assert_eq!(got, 42); assert_eq!(calls, 1); } }
经过 Miri 检测没有 UB。