22个版本
0.11.3 | 2023年10月12日 |
---|---|
0.11.0 | 2022年11月22日 |
0.10.3 | 2021年10月29日 |
0.9.2 | 2021年1月9日 |
0.6.2 | 2019年7月28日 |
#11 在 #abi-stable
28,443 每月下载量
在 33 个crate中使用(通过 abi_stable)
480KB
12K SLoC
适用于Rust-to-Rust ffi,重点在于创建在程序启动时加载的库,并具有加载时类型检查。
此库允许定义可在运行时加载的Rust库。由于默认(Rust)ABI和表示不稳定,因此这是不可能的。
此库的一些用例
-
将Rust依赖树从编译为单个二进制文件转换为单个二进制文件(以及可能多个动态库),允许在更改时单独重新编译。
-
创建一个插件系统(不支持卸载)。
特性
目前此库具有以下特性
-
sabi_trait
属性宏,用于创建ffi安全的trait对象。 -
一些trait对象的ffi安全等效,使用
DynTrait
。 -
在
std_types
模块中提供许多标准库类型的ffi安全替代品/包装器。 -
在
external_types
模块中提供外部crate中定义的一些类型的ffi安全包装器。 -
提供
StableAbi
trait,用于断言类型是ffi安全的。 -
用于构建可扩展模块和vtable的 前缀类型 功能,而不会破坏ABI兼容性。
-
支持包装在
NonExhaustive
中的ffi安全 非完整枚举。 -
在加载时检查动态库中的类型是否具有预期的布局,允许在检查类型布局的同时进行semver兼容的更改。
-
提供了
StableAbi
derive宏,用于断言类型与FFI兼容,并在加载时获取类型的布局以检查其是否仍然兼容。
变更日志
变更日志位于“Changelog.md”文件中。
示例存储库
对于使用abi_stable
的示例存储库,您可以在该存储库的示例目录中查看,在存储库中。
要运行示例存储库,您通常需要构建*_impl
存储库,然后运行*_user
存储库(所有*_user
存储库都应该有一个帮助信息)。
这些是示例存储库
-
0 - 模块和接口类型:通过命令行应用程序和动态链接的后端演示了abi_stable "模块"(函数指针的structs)和接口类型。
-
1 - 特性对象:通过创建最小插件系统演示了FFI安全的特性对象(使用
sabi_trait
属性宏生成)。 -
2 - 不完整的枚举:演示了不完整的枚举作为参数和返回值,用于管理商店目录的应用程序。
示例
这是一个完整的示例,位于examples/readme_example
,演示了
-
user crates
(在下文架构部分中定义)。 -
通过
sabi_trait
属性宏生成的FFI安全特性对象。 -
DynTrait
:一个FFI安全的多特性对象,用于一组特性,也可以向下转换为具体类型。 -
interface crates
(在下文架构部分中定义)。 -
实现存储库
(在下文架构部分中定义)。
用户存储库
此用户存储库(也称为“应用程序存储库”)依赖于接口存储库
[dependencies.readme_interface]
path = "../readme_interface"
其Rust代码是
use abi_stable::std_types::RVec;
use readme_interface::{
load_root_module_in_directory, AppenderBox, Appender_TO, BoxedInterface, ExampleLib_Ref,
};
fn main() {
// The type annotation is for the reader
let library: ExampleLib_Ref = load_root_module_in_directory("../../../target/debug".as_ref())
.unwrap_or_else(|e| panic!("{}", e));
{
/////////////////////////////////////////////////////////////////////////////////
//
// This block demonstrates `#[sabi_trait]` generated trait objects
//
////////////////////////////////////////////////////////////////////////////////
// The type annotation is for the reader
let mut appender: AppenderBox<u32> = library.new_appender()();
appender.push(100);
appender.push(200);
// The primary way to use the methods in the trait is through the inherent methods on
// the ffi-safe trait object.
Appender_TO::push(&mut appender, 300);
appender.append(vec![500, 600].into());
assert_eq!(
appender.into_rvec(),
RVec::from(vec![100, 200, 300, 500, 600])
);
}
{
///////////////////////////////////////////////////////////////////////////////////
//
// This block demonstrates the `DynTrait<>` trait object.
//
// `DynTrait` is used here as a safe opaque type which can only be unwrapped back to
// the original type in the dynamic library that constructed the `DynTrait` itself.
//
////////////////////////////////////////////////////////////////////////////////////
// The type annotation is for the reader
let mut unwrapped: BoxedInterface = library.new_boxed_interface()();
library.append_string()(&mut unwrapped, "Hello".into());
library.append_string()(&mut unwrapped, ", world!".into());
assert_eq!(&*unwrapped.to_string(), "Hello, world!");
}
println!("success");
}
注意:必须在运行之前编译实现存储库,否则您将获得运行时错误,因为库无法加载。
接口存储库
use std::path::Path;
use abi_stable::{
library::{LibraryError, RootModule},
package_version_strings, sabi_trait,
sabi_types::VersionStrings,
std_types::{RBox, RString, RVec},
DynTrait, StableAbi,
};
/// This struct is the root module,
/// which must be converted to `ExampleLib_Ref` to be passed through ffi.
///
/// The `#[sabi(kind(Prefix(prefix_ref = ExampleLib_Ref)))]`
/// attribute tells `StableAbi` to create an ffi-safe static reference type
/// for `ExampleLib` called `ExampleLib_Ref`.
///
/// The `#[sabi(missing_field(panic))]` attribute specifies that trying to
/// access a field that doesn't exist must panic with a message saying that
/// the field is inaccessible.
#[repr(C)]
#[derive(StableAbi)]
#[sabi(kind(Prefix(prefix_ref = ExampleLib_Ref)))]
#[sabi(missing_field(panic))]
pub struct ExampleLib {
pub new_appender: extern "C" fn() -> AppenderBox<u32>,
pub new_boxed_interface: extern "C" fn() -> BoxedInterface<'static>,
/// The `#[sabi(last_prefix_field)]` attribute here means that this is the last
/// field in this struct that was defined in the first compatible version of the library
/// (0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc),
/// requiring new fields to always be added below preexisting ones.
///
/// The `#[sabi(last_prefix_field)]` attribute would stay on this field until the
/// library bumps its "major" version,
/// at which point it would be moved to the last field at the time.
///
#[sabi(last_prefix_field)]
pub append_string: extern "C" fn(&mut BoxedInterface<'_>, RString),
}
/// The RootModule trait defines how to load the root module of a library.
impl RootModule for ExampleLib_Ref {
abi_stable::declare_root_module_statics! {ExampleLib_Ref}
const BASE_NAME: &'static str = "example_library";
const NAME: &'static str = "example_library";
const VERSION_STRINGS: VersionStrings = package_version_strings!();
}
/// This loads the root from the library in the `directory` folder.
pub fn load_root_module_in_directory(directory: &Path) -> Result<ExampleLib_Ref, LibraryError> {
ExampleLib_Ref::load_from_directory(directory)
}
//////////////////////////////////////////////////////////
/// `#[sabi_trait]` is how one creates an ffi-safe trait object from a trait definition.
///
/// In this case, the trait object is `Appender_TO<'lt, Pointer<()>, Element>`,where:
///
/// - `'lt`:
/// Is the lifetime bound of the type that constructed the trait object
/// (`'static` is the lifetime bound of objects that don't borrow anything).
///
/// - `Pointer<()>`:
/// Is any pointer that implements some abi_stable specific traits,
/// this pointer owns the value that implements `Appender`.
///
/// - `Element`:
/// This is the element type of the collection that we operate on.
/// This is a type parameter because it's a trait object,
/// which turn associated types into type parameters.
///
#[sabi_trait]
pub trait Appender {
/// The element type of the collection.
type Element;
/// Appends one element at the end of the collection.
fn push(&mut self, value: Self::Element);
/// Appends many elements at the end of the collection.
fn append(&mut self, vec: RVec<Self::Element>);
/// Converts this collection into an `RVec`.
///
/// As opposed to regular trait objects,
/// it is possible to call by-value methods on trait objects generated by `#[sabi_trait]`.
///
/// The `#[sabi(last_prefix_field)]` attribute here means that this is the last method
/// that was defined in the first compatible version of the library
/// (0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc),
/// requiring new methods to always be added below preexisting ones.
///
/// The `#[sabi(last_prefix_field)]` attribute would stay on this method until the library
/// bumps its "major" version,
/// at which point it would be moved to the last method at the time.
///
#[sabi(last_prefix_field)]
fn into_rvec(self) -> RVec<Self::Element>;
}
/// A type alias for the Appender trait object.
///
/// `'static` here means that the trait object cannot contain any borrows.
pub type AppenderBox<T> = Appender_TO<'static, RBox<()>, T>;
// Impls of local traits for dependencies have to be implemented in
// the interface crate, because of the orphan rules.
//
// To avoid compiling more code than necessary,
// this impl is not compiled by default.
// it's enabled by the implementation crate but not the user crate.
#[cfg(feature = "impls")]
impl<T> Appender for RVec<T> {
type Element = T;
fn push(&mut self, value: Self::Element) {
self.push(value);
}
fn append(&mut self, vec: RVec<Self::Element>) {
self.extend(vec);
}
fn into_rvec(self) -> RVec<Self::Element> {
self
}
}
//////////////////////////////////////////////////////////
/// This type implements `ÌnterfaceType`
/// (because of the `#[sabi(impl_InterfaceType())]` helper attribute of `#[derive(StableAbi)]` ),
/// describing the traits required when constructing `DynTrait<_, TheInterface>`,
/// and are then implemented by it.
#[repr(C)]
#[derive(StableAbi)]
#[sabi(impl_InterfaceType(Sync, Send, Debug, Display))]
pub struct TheInterface;
/// An alias for the trait object used in this example
pub type BoxedInterface<'borr> = DynTrait<'borr, RBox<()>, TheInterface>;
实现存储库
这是一个实现存储库,它作为cdylib(动态库/共享对象)编译,并在运行时由用户存储库加载。
其Cargo.toml文件中的重要部分
[lib]
name = "readme_library"
crate-type = ["cdylib",'rlib']
[dependencies.readme_interface]
path = "../readme_interface"
features = ["impls"]
其Rust代码是
use std::fmt::{self, Display};
use readme_interface::{AppenderBox, Appender_TO, BoxedInterface, ExampleLib, ExampleLib_Ref};
use abi_stable::{
export_root_module,
prefix_type::PrefixTypeTrait,
sabi_extern_fn,
sabi_trait::prelude::TD_Opaque,
std_types::{RString, RVec},
DynTrait,
};
/// The function which exports the root module of the library.
///
/// The root module is exported inside a static of `LibHeader` type,
/// which has this extra metadata:
///
/// - The abi_stable version number used by the dynamic library.
///
/// - A constant describing the layout of the exported root module,and every type it references.
///
/// - A lazily initialized reference to the root module.
///
/// - The constructor function of the root module.
///
#[export_root_module]
pub fn get_library() -> ExampleLib_Ref {
ExampleLib {
new_appender,
new_boxed_interface,
append_string,
}
.leak_into_prefix()
}
/// `DynTrait<_, TheInterface>` is constructed from this type in this example
#[derive(Debug, Clone)]
pub struct StringBuilder {
pub text: String,
pub appended: Vec<RString>,
}
impl Display for StringBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.text, f)
}
}
impl StringBuilder {
/// Appends the string at the end.
pub fn append_string(&mut self, string: RString) {
self.text.push_str(&string);
self.appended.push(string);
}
}
#[sabi_extern_fn]
pub fn new_appender() -> AppenderBox<u32> {
// What `TD_Opaque` does here is specify that the trait object cannot be downcasted,
// disallowing the `Appender_TO` from being unwrapped back into an `RVec<u32>`
// when the `trait_object.obj.*_downcast_*()` methods are used.
//
// To be able to unwrap a `#[sabi_trait]` trait object back into the type it
// was constructed with, you must:
//
// - Have a type that implements `std::anu::Any`
// (it requires that the type doesn't borrow anything).
//
// - Pass `TD_CanDowncast` instead of `TD_Opaque` to
// `Appender_TO::{from_const, from_value,from_ptr}`.
//
// - Unerase the trait object back into the original type with
// `trait_object.obj.downcast_into::<RVec<u32>>().unwrap()`
// (or the other downcasting methods).
//
// Downcasting a trait object will fail in any of these conditions:
//
// - It wasn't constructed in the same dynamic library.
//
// - It's not the same type.
//
// - It was constructed with `TD_Opaque`.
//
Appender_TO::from_value(RVec::new(), TD_Opaque)
}
/// Constructs a BoxedInterface.
#[sabi_extern_fn]
fn new_boxed_interface() -> BoxedInterface<'static> {
DynTrait::from_value(StringBuilder {
text: "".into(),
appended: vec![],
})
}
/// Appends a string to the erased `StringBuilder`.
#[sabi_extern_fn]
fn append_string(wrapped: &mut BoxedInterface<'_>, string: RString) {
wrapped
.downcast_as_mut::<StringBuilder>() // Returns `Result<&mut StringBuilder, _>`
.unwrap() // Returns `&mut StringBuilder`
.append_string(string);
}
安全性
此库确保通过这些机制加载的库是安全的。
-
检查库的abi_stable ABI,每个
0.y.0
版本和x.0.0
版本定义了自己的ABI,这与之前的版本不兼容。 -
在可以调用任何函数之前,在加载动态库时递归地检查类型。
请注意,此库假设动态库来自良性源,这些检查仅用于检测编程错误。
计划的功能
目前没有。
非功能(极不可能添加)
支持库卸载,因为这需要构建整个库,假设任何东西可能在任何时候卸载。
架构
这是用户可以构建其库以允许动态链接的方式。
有关如何使用abi_stable中的安全API演变动态加载的库的信息,请参阅此处。
接口存储库
一个声明
-
根模块(一个函数指针/其他模块的结构体),实现了从动态库导出的
RootModule
特性。 -
根模块的所有子模块。
-
所有传递给函数并从函数返回的公共类型。
-
可选:使用
sabi_trait
属性声明 ffi 安全的特性,在公共接口中用作特性对象。 -
可选:声明接口类型,实现
InterfaceType
的类型,用于指定DynTrait
ffi 安全特性对象中可用的特性。
实现存储库
作为动态库编译的 crate
-
实现了
interface crate
中声明的所有函数。 -
声明一个导出根模块的函数,使用
export_root_module
属性导出模块。 -
可选:实现带有
sabi_trait
属性注解的特性,构建在公共 API 中公开的特性对象。
用户存储库
声明 interface crate
作为依赖项的 crate,并从某个路径加载预先编译的 implementation crate
动态库。
最小 Rust 版本
此 crate 支持 Rust 回到 1.61.0
您可以使用 rust_*_*
cargo 功能手动启用对 Rust 1.61.0 以后版本的支持。
crate 功能
这些是默认 cargo 功能,用于启用可选的 crate
-
"channels": 依赖于
crossbeam-channel
,将其通道包装在abi_stable::external_types::crossbeam_channel
中用于 ffi。 -
"serde_json": 依赖于
serde_json
,在abi_stable::external_types::serde_json
中提供&serde_json::value::RawValue
和Box<serde_json::value::RawValue>
的 ffi 安全等效项。
要禁用默认功能,请使用
[dependencies.abi_stable]
version = "<current_version>"
default-features = false
features = [ ]
在 features
数组中启用所需的功能。
手动启用
这些是用于手动启用对新语言功能支持的 crate 功能
-
"rust_1_64": 将许多将类型转换为切片的函数转换为 const fns。
-
"rust_latest_stable": 启用所有稳定版本的 "rust_1_*" 功能。
术语表
interface crate
:声明在运行时加载库所需的公共函数、类型和特性的 crate。
ìmplementation crate
:实现接口 crate 中所有函数的 crate。
user crate
:依赖于 interface crate
并为其加载 1 个或多个 ìmplementation crate
的 crate。
模块
:指一个包含函数指针和其他静态值的结构体。库的根模块实现了RootModule
特质。这些在接口 crate
中声明,在实现 crate
中导出,并在用户 crate
中加载。
工具
以下是一些工具,它们都在“tools”目录(文件夹)中。
sabi_extract
一个程序,可以从abi_stable动态库中提取各种信息。
许可证
abi_stable根据您的要求,在以下许可证下发行:
Apache License, Version 2.0, (LICENSE-APACHE or https://apache.ac.cn/licenses/LICENSE-2.0)
MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确声明,否则根据Apache-2.0许可证定义,您提交给abi_stable的任何有意包含的贡献,将按照上述方式双重许可,不附加任何额外的条款或条件。
lib.rs
:
abi_stable的实现细节。
依赖项
~2MB
~41K SLoC