Rust跨语言交互库oneshot-uniffi的使用:高效实现Rust与其他语言的无缝通信
Rust跨语言交互库oneshot-uniffi的使用:高效实现Rust与其他语言的无缝通信
oneshot
Oneshot spsc (单生产者,单消费者) 通道。每个通道实例只能传输单个消息。这带来了几个好处:实现可以非常高效,利用只会有一个消息的知识;更重要的是,API可以这样表达:当只发送单个消息时,你不必关心某些边缘情况。例如:发送者不能被复制或克隆,发送方法会获取所有权并消耗发送者。因此,在类型级别上保证只能发送一个消息。
发送者的发送方法是非阻塞的,并且可能是无锁和无等待的。接收者支持无锁和无等待的try_recv
,以及无限和时间有限的线程阻塞接收操作。接收者还实现了Future
并支持异步等待消息。
示例
这个示例设置了一个后台工作线程,处理来自标准mpsc通道的请求,并通过每个请求提供的oneshot通道回复。由于oneshot接收器可以同步和异步接收,因此可以从同步和异步上下文中与工作线程交互。
use std::sync.mpsc;
use std::thread;
use std::time::Duration;
type Request = String;
// 启动后台线程处理发送给它的请求
// 通过oneshot通道返回响应
fn spawn_processing_thread() -> mpsc::Sender<(Request, oneshot::Sender<usize>)> {
let (request_sender, request_receiver) = mpsc::channel::<(Request, oneshot::Sender<usize>)>();
thread::spawn(move || {
for (request_data, response_sender) in request_receiver.iter() {
let compute_operation = || request_data.len();
let _ = response_sender.send(compute_operation()); // <- 在oneshot通道上发送
}
});
request_sender
}
let processor = spawn_processing_thread();
// 如果编译时启用了`std`功能,库可以在常规线程上接收带超时的消息
#[cfg(feature = "std")] {
let (response_sender, response_receiver) = oneshot::channel();
let request = Request::from("data from sync thread");
processor.send((request, response_sender)).expect("Processor down");
match response_receiver.recv_timeout(Duration::from_secs(1)) { // <- 在oneshot通道上接收
Ok(result) => println!("Processor returned {}", result),
Err(oneshot::RecvTimeoutError::Timeout) => eprintln!("Processor was too slow"),
Err(oneshot::RecvTimeoutError::Disconnected) => panic!("Processor exited"),
}
}
// 如果编译时启用了`async`功能,可以在异步上下文中等待`Receiver`
#[cfg(feature = "async")] {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
let (response_sender, response_receiver) = oneshot::channel();
let request = Request::from("data from sync thread");
processor.send((request, response_sender)).expect("Processor down");
match response_receiver.await { // <- 异步接收oneshot通道上的消息
Ok(result) => println!("Processor returned {}", result),
Err(_e) => panic!("Processor exited"),
}
});
}
完整示例
下面是一个完整的跨语言交互示例,展示如何使用oneshot-uniffi实现Rust与其他语言(如Python)的无缝通信:
// lib.rs
use uniffi_build::build_external_bindings;
#[uniffi::export]
pub struct Processor {
sender: std::sync::mpsc::Sender<(String, oneshot::Sender<usize>)>,
}
#[uniffi::export]
impl Processor {
pub fn new() -> Self {
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
for (data, response_sender) in receiver {
let result = data.len();
let _ = response_sender.send(result);
}
});
Processor { sender }
}
pub fn process(&self, data: String) -> Result<usize, String> {
let (sender, receiver) = oneshot::channel();
self.sender.send((data, sender)).map_err(|e| e.to_string())?;
receiver.recv().map_err(|e| e.to_string())
}
}
uniffi::include_scaffolding!("oneshot_example");
然后可以在Python中这样使用:
from oneshot_example import Processor
processor = Processor()
result = processor.process("Hello from Python!")
print(f"Result from Rust: {result}")
同步 vs 异步
编写这个库的主要动机是没有(我所知的)通道实现允许你在正常线程和异步任务之间无缝发送消息,或者反过来。如果消息传递是你通信的方式,当然应该在程序的同步和异步部分之间顺畅工作!
这个库通过具有快速且廉价的发送操作来实现这一点,可以在同步线程和异步任务中使用。接收者既有线程阻塞接收方法用于同步使用,也实现了Future
用于异步使用。
这个通道的接收端点实现了Rust的Future
特性,可以在异步任务中等待。这个实现完全与执行器/运行时无关。应该可以在任何执行器中使用这个库。
许可证: MIT OR Apache-2.0
Rust跨语言交互库oneshot-uniffi的使用指南
以下是基于您提供内容的完整示例demo:
Rust项目完整示例
Cargo.toml
[package]
name = "my_ffi_lib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
oneshot-uniffi = "0.1"
uniffi = "0.25"
tokio = { version = "1.0", features = ["full"] }
thiserror = "1.0"
src/lib.rs
use uniffi::*;
// 定义错误类型
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum MyError {
#[error("Invalid input")]
InvalidInput,
#[error("Division by zero")]
DivisionByZero,
#[error("Network error")]
NetworkError,
}
// 导出简单函数
#[uniffi::export]
pub fn greet(name: String) -> Result<String, MyError> {
if name.is_empty() {
return Err(MyError::InvalidInput);
}
Ok(format!("Hello, {}!", name))
}
#[uniffi::export]
pub fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
// 导出异步函数
#[uniffi::export]
async fn async_fetch(url: String) -> Result<String, MyError> {
// 模拟异步网络请求
if url.is_empty() {
return Err(MyError::InvalidInput);
}
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
Ok(format!("Fetched data from {}", url))
}
// 导出复杂类型
#[derive(uniffi::Record)]
pub struct Person {
name: String,
age: u32,
}
#[uniffi::export]
pub fn create_person(name: String, age: u32) -> Person {
Person { name, age }
}
// 导出带错误处理的函数
#[uniffi::export]
pub fn safe_divide(a: f64, b: f64) -> Result<f64, MyError> {
if b == 0.0 {
Err(MyError::DivisionByZero)
} else {
Ok(a / b)
}
}
uniffi.toml
[package]
name = "my_ffi_lib"
namespace = "my_ffi_lib"
version = "0.1.0"
[bindings]
kotlin = true
swift = true
python = true
build.rs
fn main() {
uniffi::generate_scaffolding("./src/lib.rs").unwrap();
}
各语言调用示例
Python调用示例
import asyncio
from my_ffi_lib import *
# 同步调用
print(greet("Python")) # Hello, Python!
print(add_numbers(3, 4)) # 7
# 异步调用
async def main():
result = await async_fetch("https://example.com")
print(result) # Fetched data from https://example.com
person = create_person("Alice", 30)
print(f"{person.name} is {person.age} years old") # Alice is 30 years old
try:
print(safe_divide(10.0, 2.0)) # 5.0
print(safe_divide(10.0, 0.0)) # 抛出DivisionByZero错误
except Exception as e:
print(f"Error: {e}")
asyncio.run(main())
Kotlin调用示例
import my.ffi.lib.*
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// 同步调用
println(greet("Kotlin")) // Hello, Kotlin!
println(addNumbers(5, 10)) // 15
// 异步调用
val result = asyncFetch("https://example.com")
println(result) // Fetched data from https://example.com
val person = createPerson("Bob", 25)
println("${person.name} is ${person.age} years old") // Bob is 25 years old
try {
println(safeDivide(10.0, 2.0)) // 5.0
println(safeDivide(10.0, 0.0)) // 抛出DivisionByZero异常
} catch (e: Exception) {
println("Error: $e")
}
}
Swift调用示例
import my_ffi_lib
import Foundation
Task {
// 同步调用
print(greet(name: "Swift")) // Hello, Swift!
print(addNumbers(a: 7, b: 8)) // 15
// 异步调用
let result = try await asyncFetch(url: "https://example.com")
print(result) // Fetched data from https://example.com
let person = createPerson(name: "Charlie", age: 35)
print("\(person.name) is \(person.age) years old") // Charlie is 35 years old
do {
print(try safeDivide(a: 10.0, b: 2.0)) // 5.0
print(try safeDivide(a: 10.0, b: 0.0)) // 抛出DivisionByZero错误
} catch {
print("Error: \(error)")
}
}
构建和运行步骤
- 构建Rust库:
cargo build --release
- 生成各语言绑定:
cargo run --features=uniffi/cli -- generate --language python --out-dir ./bindings ./src/lib.rs
cargo run --features=uniffi/cli -- generate --language kotlin --out-dir ./bindings ./src/lib.rs
cargo run --features=uniffi/cli -- generate --language swift --out-dir ./bindings ./src/lib.rs
- 在各语言项目中集成生成的绑定文件并运行