9 个版本 (破坏性)

0.11.0 2022 年 11 月 22 日
0.10.3 2021 年 10 月 29 日
0.10.2 2021 年 9 月 12 日
0.9.0 2020 年 11 月 21 日
0.5.0 2019 年 6 月 14 日

#1972Rust 模式

Download history 6331/week @ 2024-04-23 6602/week @ 2024-04-30 5855/week @ 2024-05-07 6375/week @ 2024-05-14 11763/week @ 2024-05-21 8548/week @ 2024-05-28 6285/week @ 2024-06-04 6228/week @ 2024-06-11 7455/week @ 2024-06-18 6301/week @ 2024-06-25 5710/week @ 2024-07-02 7461/week @ 2024-07-09 7559/week @ 2024-07-16 7532/week @ 2024-07-23 6886/week @ 2024-07-30 6265/week @ 2024-08-06

29,837 每月下载量
用于 35 个 crate (3 个直接使用)

MIT/Apache

27KB
564

Rust Join the chat at https://gitter.im/abi_stable_crates/community api-docs

适用于 Rust 到 Rust ffi,专注于创建程序启动时加载的库,并具有加载时类型检查。

此库允许定义可在运行时加载的 Rust 库。由于默认 (Rust) ABI 和表示形式不稳定,因此这是不可能的。

此库的一些用例

  • 将 Rust 依赖树从编译为单个二进制文件转换为单个二进制文件(以及可能许多动态库),允许在更改时进行单独重新编译。

  • 创建插件系统(不支持卸载)。

特性

目前,此库具有以下特性

  • 具有用于创建 ffi 安全 trait 对象的 sabi_trait 属性宏。

  • 具有 DynTrait 的某些 trait 对象的 ffi 安全等效。

  • std_types 模块中提供许多标准库类型的 ffi 安全替代方案/包装器。

  • external_types 模块中为外部 crate 定义的某些类型提供 ffi 安全包装器。

  • 提供 StableAbi trait,用于断言类型是 ffi 安全的。

  • 用于构建可扩展模块和 vtable 的 前缀类型 功能,而不会破坏 ABI 兼容性。

  • 支持用 非完整枚举 包装的 ffi 安全 NonExhaustive

  • 在加载时检查动态库中的类型具有预期的布局,允许在检查类型布局的同时进行 semver 兼容的更改。

  • 提供StableAbi derive宏,以断言类型与FFI兼容,并在加载时获取类型的布局以检查其是否仍然兼容。

变更日志

变更日志位于"Changelog.md"文件中。

示例crate

对于使用abi_stable的示例crate,您可以查看此crate仓库的examples目录中的crate。

要运行示例crate,通常需要构建*_impl crate,然后运行*_user crate(所有*_user crate都应有一个帮助信息)。

以下是一些示例crate

  • 0 - 模块和接口类型:通过具有动态链接后端的命令行应用程序,演示abi_stable的"模块"(函数指针的结构体)和接口类型。

  • 1 - 特例对象:通过创建最小的插件系统,演示FFI安全的特例对象(使用sabi_trait属性宏生成)。

  • 2 - 不完整枚举:演示不完整枚举作为参数和返回值,用于管理商店目录的应用程序。

示例

这是一个完整示例,位于examples/readme_example,演示

  • user crates(定义在下面的架构部分)。

  • 通过sabi_trait属性宏生成的FFI安全特例对象。

  • DynTrait:一个FFI安全的多特例对象,用于一组特例,也可以回溯到具体类型。

  • interface crates(定义在下面的架构部分)。

  • 实施crate(定义在下面的架构部分)。

用户crate

此用户crate(也称为"应用程序crate")依赖于接口crate

[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");
}

注意:必须在运行此之前编译实施crate,否则将出现运行时错误,因为库无法加载。

接口crate

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>;

实施crate

这是一个实施crate,它作为cdylib(动态库/共享对象)编译,并在运行时由用户crate加载。

其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_stable定义了自己的ABI,与之前的版本不兼容。

  • 在调用任何函数之前,在动态库加载时递归检查类型。

请注意,此库假定动态库来自良性的来源,这些检查纯粹是为了检测编程错误。

计划的功能

目前没有。

非功能特性(极不可能添加)

支持库卸载,因为这需要构建整个库,假设任何东西都可能随时卸载。

架构

这是用户可以结构其库以允许动态链接的一种方式。

有关如何使用abi_stable的safe API加载的动态库进行演变,请参阅此处

接口crate

一个声明

  • 根模块(函数指针/其他模块的结构体),它实现了从动态库导出的RootModule特质。

  • 根模块的所有子模块。

  • 所有传递给函数和由函数返回的公共类型。

  • 可选:使用 sabi_trait 属性声明 ffi-safe 特性,用作公共接口中的 trait 对象。

  • 可选:声明接口类型,实现 InterfaceType 的类型,用于指定 DynTrait ffi-safe 特性对象中可用的 trait。

实施crate

编译为动态库的 crate

  • 实现了在 interface crate 中声明的所有函数。

  • 声明一个导出根模块的函数,使用 export_root_module 属性导出模块。

  • 可选:实现带有 sabi_trait 属性注解的 trait,构建在公共 API 中公开的它们的 trait 对象。

用户crate

一个声明 ìnterface crate 作为依赖项的 crate,并从某个路径加载预编译的 ìmplementation 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 中提供 ffi-safe 等价物,分别为 &serde_json::value::RawValueBox<serde_json::value::RawValue>

要禁用默认特性,请使用

[dependencies.abi_stable]
version = "<current_version>"
default-features = false
features = [  ]

features 数组中启用所需的特性。

手动启用

这些是手动启用对新语言特性支持的 crate 特性

  • "rust_1_64": 将许多将类型转换为切片的函数转换为 const fns。

  • "rust_latest_stable": 启用所有稳定发布版本的 "rust_1_*" 特性。

词汇表

interface crate:声明公共函数、类型和 trait 的 crate,这些是运行时加载库所必需的。

ìmplementation crate:实现接口 crate 中所有函数的 crate。

user crate:依赖于 interface crate 并为其加载 1 个或多个 ìmplementation crate 的 crate。

模块:指一个由函数指针和其他静态值组成的结构体。库的根模块实现了RootModule特质。这些特质在interface crate中声明,在implementation crate中导出,并在user 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)

由您选择。

贡献

除非您明确说明,否则您提交给abi_stable的任何有意包含的贡献,根据Apache-2.0许可证定义,将按照上述方式双许可,不附加任何额外条款或条件。

依赖项

约450KB