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


1 回复

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

构建和运行步骤

  1. 构建Rust库:
cargo build --release
  1. 生成各语言绑定:
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
  1. 在各语言项目中集成生成的绑定文件并运行
回到顶部