如何通过Rust FFI结合swift-rs实现Swift异步函数调用?
Rust调用macOS异步Swift函数的正确FFI实现方式
一、Swift侧异步函数的C ABI包装
要把异步Swift函数转换成C兼容的导出函数,核心是用回调+Task桥接,并确保所有类型符合C ABI规范:
import Foundation // 原始异步Swift函数 func foo(arg: Int) async -> String { await Task.sleep(1_000_000_000) // 模拟异步操作 return "Result: \(arg)" } // 定义与Rust交互的C兼容字符串类型 // 若使用swift-rs,可直接引入其SRString,否则自定义如下 public struct SRString { let ptr: UnsafePointer<CChar> let len: Int public init(_ str: String) { let utf8 = str.utf8CString self.ptr = utf8.withUnsafeBufferPointer { $0.baseAddress! } self.len = utf8.count - 1 // 排除末尾null终止符 } } // C ABI导出函数:参数、回调必须符合C调用约定 @_cdecl("foo_exported") public func fooExported(arg: CInt, context: UInt64, completion: @Sendable @convention(c)(SRString, UInt64) -> Void) { // 脱离当前上下文执行异步逻辑,避免线程绑定问题 Task.detached { let result = await foo(arg: Int(arg)) completion(SRString(result), context) } }
关键注意点:
- 用
@convention(c)标记回调,确保遵循C调用约定 - 参数改用C标准类型(如
CInt)替代Swift原生Int,保证跨ABI一致性 - 异步逻辑放在
Task.detached中,避免捕获当前上下文导致的线程阻塞
二、Rust侧的FFI声明与异步封装
在Rust中需严格匹配Swift侧的函数签名,并将回调模式转换为Rust原生Future,简化异步调用:
use std::ffi::CStr; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::sync::oneshot; // 借助tokio的oneshot实现回调转Future // 与Swift侧SRString严格对齐的内存布局 #[repr(C)] #[derive(Debug)] pub struct SRString { ptr: *const i8, len: usize, } // 定义回调函数类型,必须与Swift侧签名完全匹配 type FooCompletion = unsafe extern "C" fn(SRString, u64); // 声明导出的Swift函数,需指定链接的静态库名称 #[link(name = "FooSwiftLib", kind = "static")] extern "C" { fn foo_exported(arg: i32, context: u64, completion: FooCompletion); } // 将回调模式封装为Rust Future pub async fn call_foo(arg: i32) -> String { let (sender, receiver) = oneshot::channel(); // 将oneshot Sender作为上下文传递,用于回调中返回结果 let context = sender as *mut _ as u64; unsafe { foo_exported(arg, context, completion_handler); } // 等待回调返回结果 let sr_str = receiver.await.expect("Callback failed"); unsafe { let c_str = CStr::from_ptr(sr_str.ptr); let result = c_str.to_str().unwrap().to_string(); // 若Swift侧未自动释放内存,需手动执行释放(根据实际内存策略调整) // std::alloc::dealloc(sr_str.ptr as *mut u8, std::alloc::Layout::new::<u8>()); result } } // Rust侧回调处理函数,负责将结果转发给oneshot通道 unsafe extern "C" fn completion_handler(sr_str: SRString, context: u64) { let sender = context as *mut oneshot::Sender<SRString>; let _ = (*sender).send(sr_str); // 释放sender内存 std::alloc::dealloc(sender as *mut u8, std::alloc::Layout::new::<oneshot::Sender<SRString>>()); }
关键注意点:
- 结构体必须添加
#[repr(C)],保证内存布局与Swift侧一致 #[link(...)]需指定Swift编译生成的静态库名称,确保链接正确- 用
oneshot::channel将回调逻辑转换为Rust Future,适配Rust异步生态
三、符号查找错误的排查与解决
你遇到的Failed to look up symbolic reference错误,通常由以下原因导致:
- 函数名不匹配:Swift侧
@_cdecl的名称必须与Rust侧extern声明完全一致(注意下划线、大小写) - 链接配置缺失:未正确将Swift静态库链接到Rust项目。编译Swift为静态库的命令示例:
并在Rust的swiftc -static -emit-library Foo.swift -o libFooSwiftLib.aCargo.toml中添加链接路径:[profile.dev] rustflags = ["-L", "./path/to/swift/lib"] - 类型不兼容:避免使用Swift原生类型作为FFI参数,必须改用C标准类型(如
CInt、UInt64) - 模块导入问题:若使用swift-rs的
SRString,需确保Swift代码正确导入swift_rs模块
内容的提问来源于stack exchange,提问作者mjg rara
相关产品推荐
相关产品推荐

