7 个版本
0.5.0 | 2023年6月19日 |
---|---|
0.4.1 | 2023年2月4日 |
0.3.4 | 2023年1月17日 |
0.3.2 | 2022年8月30日 |
在 Rust 模式 中排名第 1618
每月下载量 33 次
115KB
1.5K SLoC
该软件包提供了一种方便从 Rust 和 C 中使用的字符串抽象。它提供了一种将字符串传递到 Rust 函数并从 C 返回字符串的方式,并具有明确的所有权规则。
用法
此软件包中的类型使用 ffizz_passby::UnboxedStruct
,并具有相似的 API。有关创建有效 C API 的一般指导,请参阅 ffizz-passby
软件包的文档。
字符串类型
在您的 C 头文件中将 C 类型 fz_string_t
作为与 fz_string_t
文档字符串中相同的结构体公开。这足以容纳 FzString
类型,并确保 C 编译器将正确对齐值。
您可以为该类型命名。类型名称在 C ABI 中被擦除,因此可以编写使用 fz_string_t
的 Rust 声明和等效的 C 声明使用 mystrtype_t
。如果您愿意,还可以使用 use ffizz_string::fz_string_t as ..
重命名 Rust 类型。
字符串实用函数
该软件包包括一些名为 fz_string_..
的实用函数。您可以使用您喜欢的任何名称将这些函数重新导出到 C,并使用基于本软件包中那些的文档字符串,包括 C 声明
ffizz_snippet!{
#[ffizz(name="mystrtype_free")]
/// Free a mystrtype_t.
///
/// # Safety
///
/// The string must not be used after this function returns, and must not be freed more than once.
/// It is safe to free Null-variant strings.
///
/// ```c
/// EXTERN_C void mystrtype_free(mystrtype_t *);
/// ```
}
ffizz_string::reexport!(fz_string_free as mystrtype_free);
将字符串作为函数参数
接受字符串作为函数参数时,需要做出两个设计决策。首先,字符串的所有权是否从调用者转移到被调用者?或者用 Rust 术语来说,值是否被移动?这主要是调用者方便的问题,但最好在整个 API 中保持一致。
其次,您是想按值传递字符串还是按指针传递?按指针传递是推荐的,因为它通常更有效,并允许以防止使用后释放错误的方式使已移动的值无效。
按指针
定义你的 extern "C"
函数,使其接受一个 *mut fz_string_t
参数
pub unsafe extern "C" fn is_a_color_name(name: *const fz_string_t) -> bool { .. };
如果需要获取值的所有权,使用 FzString::take_ptr
。否则,使用 FzString::with_ref
或 FzString::with_ref_mut
从指针借用一个引用。
所有这些方法都是不安全的。按照标准做法,调用每个不安全方法时,都要在“安全性”部分的每个条目上进行地址解析。这些通常可以参考C头文件中的文档字符串,因为这通常是由C调用者确保满足这些要求的责任。例如
ffizz_snippet!{
#[ffizz(name="mystrtype_free")]
/// Determine whether the given string contains a color name.
///
/// # Safety
///
/// The name argument must not be NULL.
///
/// ```c
/// EXTERN_C bool is_a_color_name(const fz_string_t *);
/// ```
}
pub unsafe extern "C" fn is_a_color_name(name: *const fz_string_t) -> bool { .. };
// SAFETY:
// - name is not NULL (see docstring)
// - no other thread will mutate name (type is documented as not threadsafe)
unsafe {
FzString::with_ref(name, |name| {
if let Some(name) = name.as_str() {
return Colors::from_str(name).is_some();
}
false // invalid UTF-8 is _not_ a color name
})
}
按值传递
或者,你可能需要调用者按值传递字符串。这样声明你的函数
pub unsafe extern "C" fn is_a_color_name(name: fz_string_t) -> bool { .. };
然后,使用 FzString::take
来获取字符串的所有权。按值传递时,调用者没有保留所有权的选项。
始终获取一切
如果你的C API定义表明函数接受其函数参数中的值的所有权,在发生任何早期返回之前,获取所有参数的所有权。例如
pub unsafe extern "C" convolve_strings(a: *const fz_string_t, b: *const fz_string_t) -> bool {
// SAFETY: ...
let a = unsafe { FzString::take_ptr(a) };
if a.len() == 0 {
return false; // BUG
}
// SAFETY: ...
let b = unsafe { FzString::take_ptr(b) }; // BAD!
// ...
}
在这里,如果 a
无效,函数将不会释放 b
,尽管API合同承诺这样做。为了修复,将 let b
语句移动到早期返回之前。
字符串作为返回值
要返回一个字符串,定义你的 extern "C"
函数,使其返回一个 fz_string_t
pub unsafe extern "C" fn favorite_color() -> fz_string_t { .. }
然后使用 FzString::return_val
来返回值
pub unsafe extern "C" fn favorite_color() -> fz_string_t {
let color = FzString::from("raw umber");
// SAFETY:
// - caller will free the returned string (see docstring)
unsafe {
return FzString::return_val(color);
}
}
字符串作为输出参数
在C和C++中,“输出参数”是一种常见的惯用语。为了将字符串返回到输出参数,使用 FzString::to_out_param
或 FzString::to_out_param_nonnull
/// Determine the complement of the given color, returning true on success. If
/// the color cannot be complemented, return false and leave the
/// `complement_out` string uninitialized.
pub unsafe extern "C" fn complement_color(
color: *const fz_string_t,
complement_out: *mut fz_string_t) -> fz_string_t {
result = FzString::from("opposite");
unsafe {
FzString::to_out_param(complement_out, result);
}
true
}
线程安全性
通常,fz_string_t
不适合在多个线程中并发使用(在Rust术语中,它不是 Sync
),但可以在线程之间传递(Send
)。
更准确地说,接受 *const fz_string_t
的函数,相当于一个共享借用,可以与同一字符串并发调用。然而,任何接受 *mut fz_string_t
的函数,相当于一个独占借用,不得与任何其他接受同一字符串的函数并发调用。几个实用函数内部会修改字符串,因此接受 *mut fz_string_t
在许多情况下,可能只需要在C头文件中记录线程安全的第一、一般定义,以避免在C API中不必要的复杂性。
示例
请参阅本仓库中的 示例代码 kv
,其中展示了如何使用 ffizz_string
的简单库。
性能
实现是通用的,可能会导致比必要更多的分配或字符串复制。特别是当 Rust 实现立即将 FzString
转换为 std::string::String
时,这种情况更为明显。这种转换带来了极大的简化,但涉及到字符串的分配和复制。
在 API 性能至关重要的场景下,可能更倾向于在整个实现中使用 FzString
。
依赖项
~2MB
~45K SLoC