You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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错误,通常由以下原因导致:

  1. 函数名不匹配:Swift侧@_cdecl的名称必须与Rust侧extern声明完全一致(注意下划线、大小写)
  2. 链接配置缺失:未正确将Swift静态库链接到Rust项目。编译Swift为静态库的命令示例:
    swiftc -static -emit-library Foo.swift -o libFooSwiftLib.a
    
    并在Rust的Cargo.toml中添加链接路径:
    [profile.dev]
    rustflags = ["-L", "./path/to/swift/lib"]
    
  3. 类型不兼容:避免使用Swift原生类型作为FFI参数,必须改用C标准类型(如CInt、UInt64)
  4. 模块导入问题:若使用swift-rs的SRString,需确保Swift代码正确导入swift_rs模块

内容的提问来源于stack exchange,提问作者mjg rara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 19:46:21