Rust跨语言FFI工具库diplomat_core的使用:简化Rust与其他语言交互的代码生成与绑定
Rust跨语言FFI工具库diplomat_core的使用:简化Rust与其他语言交互的代码生成与绑定
简介
Diplomat是一个实验性的Rust工具,用于生成FFI(外部函数接口)定义,允许其他语言调用Rust代码。使用Diplomat,您可以简单地定义要通过FFI公开的Rust API,并自动获得高级的C、C++和JavaScript绑定!
Diplomat支持从Rust生成以下语言的绑定:
- C
- C++
- Dart
- JavaScript/TypeScript
- Kotlin (使用JNA)
- Python (使用nanobind)
安装
首先安装用于生成绑定的CLI工具:
$ cargo install diplomat-tool
然后将Diplomat宏和运行时添加为项目的依赖项:
diplomat = "0.10.0"
diplomat-runtime = "0.10.0"
完整示例
以下是一个使用Diplomat生成跨语言绑定的完整示例:
- 首先创建一个Rust库项目:
$ cargo new --lib my_ffi_lib
$ cd my_ffi_lib
- 编辑Cargo.toml添加依赖:
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
diplomat = "0.10.0"
diplomat-runtime = "0.10.0"
- 创建src/lib.rs定义API:
use diplomat_core::diplomat_runtime::{DiplomatResult, DiplomatWriteable};
use std::fmt::Write;
#[diplomat::bridge]
pub mod ffi {
use super::*;
#[diplomat::opaque]
pub struct MyString(String);
impl MyString {
/// 创建一个新的MyString
pub fn new(s: &str) -> Box<MyString> {
Box::new(MyString(s.to_string()))
}
/// 获取字符串长度
pub fn len(&self) -> usize {
self.0.len()
}
/// 将字符串内容写入提供的Writeable
pub fn write(&self, w: &mut DiplomatWriteable) -> DiplomatResult<(), ()> {
write!(w, "{}", self.0).map_err(|_| ())
}
}
/// 计算两个数的和
pub fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
}
- 生成绑定代码:
$ diplomat-tool c ./generated/c --docs ./generated/docs
$ diplomat-tool cpp ./generated/cpp --docs ./generated/docs
- 现在可以在其他语言中使用生成的绑定了:
C++示例:
#include <iostream>
#include "generated/cpp/my_ffi_lib.hpp"
int main() {
auto str = my_ffi_lib::MyString::new_("Hello from Rust!");
std::cout << "String length: " << str->len() << std::endl;
int sum = my_ffi_lib::add_numbers(5, 7);
std::cout << "5 + 7 = " << sum << std::endl;
return 0;
}
架构与测试
Diplomat使用快照测试来检查宏和代码生成逻辑。当代码生成逻辑更改需要更新快照时,运行cargo insta review
(运行cargo install cargo-insta
获取工具)查看更改并更新快照。
JavaScript/WASM支持
对于wasm32-unknown-unknown
目标的JavaScript绑定,有两个选项:
- 使用nightly Rust并启用
-Zwasm-c-abi=spec
标志 - 配置JS后端使用旧版绑定
总结
Diplomat通过以下方式简化了Rust与其他语言的交互:
- 提供简单的属性宏来标记要导出的API
- 自动生成类型安全的绑定代码
- 支持多种目标语言
- 处理复杂的FFI边界问题
这使得在Rust中编写高性能核心逻辑,同时提供多语言接口变得非常简单。
完整示例demo
基于上述内容,这里提供一个更完整的Rust FFI示例:
- 首先创建项目结构:
cargo new --lib rust_ffi_demo
cd rust_ffi_demo
mkdir -p generated/{c,cpp}
- 编辑Cargo.toml:
[package]
name = "rust_ffi_demo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
diplomat = "0.10.0"
diplomat-runtime = "0.10.0"
serde = { version = "1.0", features = ["derive"] }
- 创建src/lib.rs:
use diplomat_core::diplomat_runtime::{DiplomatResult, DiplomatWriteable};
use std::fmt::Write;
use serde::{Serialize, Deserialize};
#[diplomat::bridge]
pub mod ffi {
use super::*;
#[diplomat::opaque]
#[derive(Serialize, Deserialize)]
pub struct Person {
name: String,
age: u32,
}
impl Person {
/// 创建一个新的Person
pub fn new(name: &str, age: u32) -> Box<Person> {
Box::new(Person {
name: name.to_string(),
age,
})
}
/// 获取年龄
pub fn get_age(&self) -> u32 {
self.age
}
/// 将个人信息转为JSON字符串
pub fn to_json(&self, w: &mut DiplomatWriteable) -> DiplomatResult<(), ()> {
let json = serde_json::to_string(self).map_err(|_| ())?;
write!(w, "{}", json).map_err(|_| ())
}
/// 从JSON字符串创建Person
pub fn from_json(json: &str) -> DiplomatResult<Box<Person>, ()> {
serde_json::from_str(json)
.map(Box::new)
.map_err(|_| ())
}
}
/// 计算斐波那契数列
pub fn fibonacci(n: u32) -> u64 {
if n == 0 {
return 0;
}
let mut a = 0;
let mut b = 1;
for _ in 1..n {
let c = a + b;
a = b;
b = c;
}
b
}
}
- 生成绑定代码:
diplomat-tool c ./generated/c
diplomat-tool cpp ./generated/cpp
- C++调用示例 (main.cpp):
#include <iostream>
#include "generated/cpp/rust_ffi_demo.hpp"
int main() {
// 创建Person对象
auto person = rust_ffi_demo::Person::new_("Alice", 30);
std::cout << "Age: " << person->get_age() << std::endl;
// 序列化为JSON
rust_ffi_demo::DiplomatWriteable writeable;
person->to_json(&writeable);
std::cout << "JSON: " << writeable.to_string_view() << std::endl;
// 从JSON反序列化
auto json_str = "{\"name\":\"Bob\",\"age\":25}";
auto new_person = rust_ffi_demo::Person::from_json(json_str).unwrap();
std::cout << "New person age: " << new_person->get_age() << std::endl;
// 计算斐波那契数列
std::cout << "Fibonacci(10): " << rust_ffi_demo::fibonacci(10) << std::endl;
return 0;
}
- 编译和运行:
# 编译Rust库
cargo build --release
# 编译C++程序 (假设使用g++)
g++ -std=c++17 -I./generated/cpp main.cpp -L./target/release -lrust_ffi_demo -o demo
# 运行程序
LD_LIBRARY_PATH=./target/release ./demo
这个完整示例展示了:
- 定义复杂数据结构(Person)
- 使用serde进行JSON序列化/反序列化
- 导出方法和函数到C++
- 处理字符串和错误返回
- 完整的构建和运行流程
Rust跨语言FFI工具库diplomat_core使用指南
介绍
diplomat_core是一个用于简化Rust与其他语言交互的代码生成工具库,它专注于提供安全、高效的跨语言FFI(外部函数接口)解决方案。该工具由Rust核心团队开发,旨在解决传统FFI绑定中的复杂性和安全性问题。
主要特点:
- 自动生成多种语言的绑定代码
- 提供类型安全的接口
- 减少手动编写FFI代码的工作量
- 支持多种目标语言(如C, C++, JavaScript等)
- 内置内存安全保证
安装
首先在Cargo.toml中添加依赖:
[dependencies]
diplomat_core = "0.7"
基本使用方法
1. 定义接口
创建一个Rust模块并使用diplomat::bridge
宏标记需要导出的API:
#[diplomat::bridge]
mod ffi {
pub struct MyStruct {
value: i32,
}
impl MyStruct {
pub fn new(value: i32) -> MyStruct {
MyStruct { value }
}
pub fn get_value(&self) -> i32 {
self.value
}
pub fn double(&mut self) {
self.value *= 2;
}
}
}
2. 生成绑定代码
使用Diplomat CLI工具生成目标语言的绑定代码:
cargo install diplomat-tool
diplomat-tool gen --language=cpp --out=bindings/
3. 使用生成的绑定
C++示例
#include "bindings/my_struct.hpp"
#include <iostream>
int main() {
auto my_struct = my_namespace::MyStruct::new_(42);
std::cout << "Value: " << my_struct.get_value() << std::endl;
my_struct.double();
std::cout << "Doubled: " << my_struct.get_value() << std::endl;
return 0;
}
JavaScript示例
const { MyStruct } = require('./bindings/my_struct');
const myStruct = MyStruct.new(42);
console.log(`Value: ${myStruct.getValue()}`);
myStruct.double();
console.log(`Doubled: ${myStruct.getValue()}`);
高级功能
1. 错误处理
#[diplomat::bridge]
mod ffi {
#[derive(Debug)]
pub enum MyError {
InvalidInput,
OperationFailed,
}
impl MyStruct {
pub fn try_operation(&self) -> Result<(), MyError> {
if self.value < 0 {
Err(MyError::InvalidInput)
} else {
Ok(())
}
}
}
}
2. 复杂类型支持
#[diplomat::bridge]
mod ffi {
pub struct ComplexData {
data: Vec<String>,
}
impl ComplexData {
pub fn new() -> Self {
ComplexData { data: Vec::new() }
}
pub fn add_item(&mut self, item: &str) {
self.data.push(item.to_string());
}
pub fn get_items(&self) -> Vec<&str> {
self.data.iter().map(|s| s.as_str()).collect()
}
}
}
3. 回调函数
#[diplomat::bridge]
mod ffi {
pub type Callback = Box<dyn Fn(i32) -> i32>;
impl MyStruct {
pub fn apply_callback(&self, callback: Callback) -> i32 {
callback(self.value)
}
}
}
配置选项
可以在项目的diplomat.toml
中配置生成选项:
[bindings.cpp]
namespace = "my_project"
include_prefix = "my_project"
[bindings.js]
module_name = "my-project"
最佳实践
- 保持FFI接口简单,复杂的逻辑应该在Rust侧实现
- 尽量减少跨语言边界的数据传输
- 使用Diplomat提供的类型而不是原始类型
- 为所有可能失败的操作提供明确的错误处理
- 编写跨语言测试确保绑定正常工作
限制
- 目前仍在积极开发中,API可能会有变化
- 某些高级Rust特性可能不受支持
- 性能敏感场景可能需要手动优化
通过使用diplomat_core,开发者可以大大简化Rust与其他语言交互的复杂性,同时保持类型安全和内存安全。
完整示例Demo
下面是一个完整的Rust项目示例,展示如何使用diplomat_core创建跨语言FFI绑定:
Rust项目结构
my_ffi_project/
├── Cargo.toml
├── diplomat.toml
├── src/
│ ├── lib.rs
└── bindings/
Cargo.toml
[package]
name = "my_ffi_project"
version = "0.1.0"
edition = "2021"
[dependencies]
diplomat_core = "0.7"
diplomat.toml
[bindings.cpp]
namespace = "my_ffi"
include_prefix = "my_ffi"
[bindings.js]
module_name = "my-ffi"
src/lib.rs
#[diplomat::bridge]
mod ffi {
pub struct Calculator {
value: f64,
}
impl Calculator {
pub fn new(initial: f64) -> Self {
Calculator { value: initial }
}
pub fn add(&mut self, x: f64) {
self.value += x;
}
pub fn subtract(&mut self, x: f64) {
self.value -= x;
}
pub fn multiply(&mut self, x: f64) {
self.value *= x;
}
pub fn divide(&mut self, x: f64) -> Result<(), CalcError> {
if x == 0.0 {
Err(CalcError::DivisionByZero)
} else {
self.value /= x;
Ok(())
}
}
pub fn get_result(&self) -> f64 {
self.value
}
}
#[derive(Debug)]
pub enum CalcError {
DivisionByZero,
InvalidOperation,
}
}
生成绑定
cargo install diplomat-tool
diplomat-tool gen --language=cpp --out=bindings/
diplomat-tool gen --language=js --out=bindings/
C++使用示例
#include "bindings/calculator.hpp"
#include <iostream>
int main() {
auto calc = my_ffi::Calculator::new_(10.0);
calc.add(5.0);
calc.multiply(2.0);
if (auto result = calc.divide(3.0); result.is_err()) {
std::cerr << "Calculation error!" << std::endl;
return 1;
}
std::cout << "Result: " << calc.get_result() << std::endl;
return 0;
}
JavaScript使用示例
const { Calculator, CalcError } = require('./bindings/calculator');
const calc = Calculator.new(10.0);
calc.add(5.0);
calc.multiply(2.0);
try {
calc.divide(3.0);
console.log(`Result: ${calc.getResult()}`);
} catch (e) {
if (e === CalcError.DivisionByZero) {
console.error("Cannot divide by zero!");
} else {
console.error("Calculation error!");
}
}
这个完整示例展示了如何创建一个简单的计算器FFI接口,并在C++和JavaScript中使用它,包括错误处理功能。