11个不稳定版本 (4个破坏性版本)
0.5.0 | 2023年6月19日 |
---|---|
0.4.1 | 2023年2月4日 |
0.3.4 | 2023年1月17日 |
0.3.2 | 2022年8月30日 |
0.1.1 | 2022年8月11日 |
#446 in 数据结构
每月下载量69
用于 ffizz-string
46KB
557 行
此crate提供了一些有用的类型,用于实现C API到Rust库的转换。这些结构体提供了鼓励使用规范、安全的C API的实用函数。
典型用法是在提供C API的crate中,包含一系列#[repr(C)]
类型和一些#[no_mangle]
函数,这些函数可以从C调用。这种风格的C API通常为每个类型有几种“方法”。例如,在palmtree
库中,一个Node
类型可能有几个C可访问的字段和C“方法”函数,如palmtree_node_new
、palmtree_node_free
和palmtree_node_coconut
。这些函数使用此crate中的方法将值安全地在C和Rust之间传递。
用法
提供的每个类型都是一个空结构体,暴露了实用方法。要使用这些类型,定义一个类型别名,指定相关的Rust和(如果需要)C类型
# struct InfPrec(u32);
# #[repr(C)]
# struct infprec_t(u32);
# impl From<InfPrec> for infprec_t {
# fn from(rval: InfPrec) -> infprec_t { infprec_t(rval.0) }
# }
# impl Into<InfPrec> for infprec_t {
# fn into(self) -> InfPrec { InfPrec(self.0) }
# }
type InfPrecValue = ffizz_passby::Value<InfPrec, infprec_t>;
然后,根据需要在该类型上使用实用函数
# struct InfPrec(u32);
# #[repr(C)]
# struct infprec_t(u32);
# impl From<InfPrec> for infprec_t {
# fn from(rval: InfPrec) -> infprec_t { infprec_t(rval.0) }
# }
# impl Into<InfPrec> for infprec_t {
# fn into(self) -> InfPrec { InfPrec(self.0) }
# }
# type InfPrecValue = ffizz_passby::Value<InfPrec, infprec_t>;
let unlucky = InfPrec(13);
// SAFETY: value does not contain an allocation
unsafe {
let cval = InfPrecValue::return_val(unlucky);
}
以下类型可用
Value
,允许在C和Rust之间传递简单的Copy
-able值。Boxed
,允许通过指针传递值,其中Rust管理分配。Unboxed
,允许通过指针传递值,但C管理分配(如在栈上或在某些其他结构体中)。
安全性
此crate不会自动使任何内容安全。它只是将C和Rust之间值处理的处理方式限制在几种方法中。一旦理解了这些方法,Rust和C程序员更容易理解和遵守安全要求。
本仓库中的每个不安全特性格式方法都描述了其安全性要求。对这些方法中的任何一个的调用都必须在一个unsafe { }
代码块中,并且该代码块之前应该有一个注释来针对每个安全性要求进行说明。这在文档中的示例中得到了说明。
一般建议
一般来说,C API依赖于程序员仔细阅读API文档并遵循其规则,而不需要编译器的帮助。在可能的情况下,设计API使其变得简单,有易于记忆的规则、一致的行为,以及在实用的运行时进行检查。
获取所有权
当一个函数被记录为获取一个值的所有权时,确保在发生错误的情况下也总是这样,例如,考虑一个无限精度除法函数
# struct InfPrec(u32);
# impl InfPrec {
# fn is_zero(&self) -> bool { self.0 == 0 }
# }
# impl std::ops::Div<InfPrec> for InfPrec {
# type Output = Self;
# fn div(self, other: Self) -> Self { Self(self.0 / other.0) }
# }
# #[repr(C)]
# struct infprec_t(u32);
type InfinitePrecision = ffizz_passby::Unboxed<InfPrec, infprec_t>;
unsafe extern "C" fn infprec_div(a: infprec_t, b: infprec_t, c_out: *mut infprec_t) -> bool {
// SAFETY: b is valid and caller will not use it again (documented in API)
let b = unsafe { InfinitePrecision::take(b) };
if b.is_zero() {
return false;
}
// SAFETY: a is valid and caller will not use it again (documented in API)
let a = unsafe { InfinitePrecision::take(a) };
// SAFETY:
// - c_out is not NULL, properly aligned, and has enough space for an infprec_t (documented in API)
// - c_out will eventually be passed back to Rust to be freed.
unsafe {
InfinitePrecision::to_out_param_nonnull(a / b, c_out);
}
true
}
调用者期望infprec_div
获取a
和b
的所有权,并且在返回时不会释放它们。按照现在的写法,当b
为零时,在a
转换为Rust值之前就发生了提前返回,所以它不会被丢弃,并且会泄漏。在这种情况下,修复方法是提前将let a
语句移动到提前返回之前。
隐藏的可变性
Rust在共享、只读引用和独占、可变引用之间做出了严格的区分。C通常不做出这样的区分,并通过仔细的注释来说明哪些方法可以同时调用,以避免数据竞争。在大多数情况下,C程序员会“猜测”哪些方法可以同时调用。一个好的C API会明确这些猜测。
例如,一个类型可能被记录为不安全地跨线程,这意味着一个类型的单个值上不能同时调用任何函数。然而,对于许多类型,只要它们不是修改操作,只读方法就可以同时调用。例如,可以在不与任何kvstore_set
调用重叠的情况下,同时调用kvstore_get
。
内部可变性需要更加仔细的文档。继续上面的例子,如果kvstore_t
数据结构在读取时自行重新平衡,在这种情况下,即使它看起来是一个只读操作,调用kvstore_get
也是不安全的。在Rust中,签名将是KVStore::get(&mut self)
,并且编译器将阻止这种并发调用。在C中,这必须在文档中清楚地解释。