Rust UniFFI跨平台开发:从核心逻辑到多语言绑定的6种生产模式

编程语言

用3种语言写同一套逻辑,你还没疯吗

你的加密模块用Rust写了一遍,Android端用Kotlin重写一遍,iOS端用Swift再写一遍,Python服务端还得来一遍。四个代码库,四套Bug,四次安全审计。更绝望的是:Kotlin版的AES和Swift版的AES行为不一致,排查了三天才发现是填充模式不同。

2026年,Rust UniFFI给出了终极答案:核心逻辑写一次Rust,自动生成Kotlin/Swift/Python绑定。Mozilla出品,Firefox和Application Services已经在生产环境跑了三年。这不是玩具项目,这是跨平台Rust的工业级方案。

本文将从UDL接口定义出发,带你完成UDL类型系统→Kotlin Android集成→Swift iOS集成→Python桌面绑定→异步回调→错误处理的6种生产模式,让Rust UniFFI跨平台开发从"能跑"变成"能上生产"。


核心要点

  • UniFFI通过UDL文件定义接口,自动生成多语言绑定代码
  • 支持Kotlin/Swift/Python/Ruby等7+语言绑定
  • 异步操作通过回调机制实现,不阻塞宿主语言主线程
  • 错误处理自动映射:Rust枚举→Kotlin sealed class/Swift enum/Python Exception
  • 类型转换零拷贝:基础类型直接映射,复杂类型通过FFI桥接
  • 移动端集成:Android通过JNA,iOS通过静态库链接

目录

  1. UniFFI核心概念
  2. 模式1:UDL接口定义与类型系统
  3. 模式2:Kotlin绑定与Android集成
  4. 模式3:Swift绑定与iOS集成
  5. 模式4:Python绑定与桌面应用
  6. 模式5:异步操作与回调
  7. 模式6:错误处理与类型转换
  8. 5个常见坑及解决方案
  9. 10个常见报错排查
  10. 进阶优化技巧
  11. 对比分析:UniFFI vs JNI vs FFI vs CXX
  12. 在线工具推荐

UniFFI核心概念

概念 说明
UDL UniFFI Definition Language,接口定义语言,描述跨语言API
Scaffold 根据UDL生成的Rust骨架代码
Binding 根据UDL生成的目标语言绑定代码
FFI Foreign Function Interface,Rust与宿主语言的互操作层
Lift/Lower 类型转换:Lower(Rust→FFI),Lift(FFI→宿主语言)
Object 跨语言共享的Rust对象,通过Arc引用计数管理
Callback 守宿主语言实现的接口,Rust侧回调调用
Record 跨语言传递的数据结构,类似DTO
Enum 跨语言传递的枚举类型
Error 跨语言传递的错误类型

UniFFI架构流程

┌─────────────────────────────────────────────────────────┐
│                    UniFFI Architecture                    │
├─────────────────────────────────────────────────────────┤
│                                                           │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐              │
│  │ Kotlin  │    │  Swift  │    │ Python  │              │
│  │ Binding │    │ Binding │    │ Binding │              │
│  └────┬────┘    └────┬────┘    └────┬────┘              │
│       │              │              │                     │
│       ▼              ▼              ▼                     │
│  ┌────────────────────────────────────────┐              │
│  │           FFI Bridge (C ABI)           │              │
│  │     Lower(Rust→C) / Lift(C→Host)      │              │
│  └──────────────────┬─────────────────────┘              │
│                     │                                     │
│                     ▼                                     │
│  ┌────────────────────────────────────────┐              │
│  │         Rust Core Library              │              │
│  │   ┌──────┐ ┌──────┐ ┌──────┐         │              │
│  │   │Crypto│ │ Auth │ │ Data │         │              │
│  │   └──────┘ └──────┘ └──────┘         │              │
│  └────────────────────────────────────────┘              │
│                                                           │
│  UDL ──→ uniffi-bindgen ──→ Scaffold + Bindings          │
│                                                           │
└─────────────────────────────────────────────────────────┘

模式1:UDL接口定义与类型系统

UDL是UniFFI的核心——它定义了Rust和所有宿主语言之间的契约。

项目结构

crypto-core/
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── crypto.rs
│   ├── auth.rs
│   └── error.rs
└── src/crypto_core.udl

Cargo.toml配置

[package]
name = "crypto-core"
version = "0.1.0"
edition = "2021"

[lib]
name = "crypto_core"
crate-type = ["cdylib", "staticlib", "lib"]

[dependencies]
uniffi = "0.28"
thiserror = "2.0"
sha2 = "0.10"
hmac = "0.12"
aes-gcm = "0.10"
rand = "0.8"

[build-dependencies]
uniffi = { version = "0.28", features = ["build"] }

[dev-dependencies]
uniffi = { version = "0.28", features = ["bindgen-tests"] }

UDL接口定义

namespace crypto_core {
    string hash_password(string password, string salt);
    string generate_token(string user_id, i64 expiry_seconds);
};

dictionary PasswordHashResult {
    string hash;
    string salt;
    i32 iterations;
};

dictionary TokenPayload {
    string user_id;
    i64 issued_at;
    i64 expires_at;
    boolean is_valid;
};

enum EncryptionAlgorithm {
    "aes-256-gcm",
    "chacha20-poly1305",
    "xchacha20-poly1305",
};

[Error]
enum CryptoError {
    InvalidKey,
    EncryptionFailed(string reason),
    DecryptionFailed(string reason),
    TokenExpired,
    InvalidToken(string reason),
};

[Throws=CryptoError]
dictionary EncryptedData {
    bytes ciphertext;
    bytes nonce;
    bytes tag;
    EncryptionAlgorithm algorithm;
};

interface CryptoService {
    [Throws=CryptoError]
    EncryptedData encrypt(bytes plaintext, bytes key, EncryptionAlgorithm algorithm);

    [Throws=CryptoError]
    bytes decrypt(EncryptedData data, bytes key);

    [Throws=CryptoError]
    PasswordHashResult hash_password_secure(string password, i32 iterations);

    [Throws=CryptoError]
    boolean verify_password(string password, PasswordHashResult stored_hash);

    [Throws=CryptoError]
    string generate_token(string user_id, i64 expiry_seconds);

    [Throws=CryptoError]
    TokenPayload validate_token(string token);
};

interface AuthService {
    constructor(string secret_key, i64 default_token_expiry);

    [Throws=CryptoError]
    string login(string user_id, string password);

    [Throws=CryptoError]
    TokenPayload verify(string token);

    void revoke_token(string token);
};

callback interface ProgressCallback {
    void on_progress(i32 current, i32 total);
    void on_complete(string result);
};

Rust实现

uniffi::include_scaffolding!("crypto_core");

use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use aes_gcm::aead::Aead;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use rand::RngCore;

type HmacSha256 = Hmac<Sha256>;

#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum CryptoError {
    #[error("Invalid key")]
    InvalidKey,

    #[error("Encryption failed: {reason}")]
    EncryptionFailed { reason: String },

    #[error("Decryption failed: {reason}")]
    DecryptionFailed { reason: String },

    #[error("Token expired")]
    TokenExpired,

    #[error("Invalid token: {reason}")]
    InvalidToken { reason: String },
}

#[derive(Debug, uniffi::Record)]
pub struct PasswordHashResult {
    pub hash: String,
    pub salt: String,
    pub iterations: i32,
}

#[derive(Debug, uniffi::Record)]
pub struct TokenPayload {
    pub user_id: String,
    pub issued_at: i64,
    pub expires_at: i64,
    pub is_valid: bool,
}

#[derive(Debug, uniffi::Enum)]
pub enum EncryptionAlgorithm {
    Aes256Gcm,
    ChaCha20Poly1305,
    XChaCha20Poly1305,
}

#[derive(Debug, uniffi::Record)]
pub struct EncryptedData {
    pub ciphertext: Vec<u8>,
    pub nonce: Vec<u8>,
    pub tag: Vec<u8>,
    pub algorithm: EncryptionAlgorithm,
}

#[derive(uniffi::Object)]
pub struct CryptoService;

#[uniffi::export]
impl CryptoService {
    #[uniffi::constructor]
    pub fn new() -> Self {
        Self
    }

    pub fn encrypt(
        &self,
        plaintext: Vec<u8>,
        key: Vec<u8>,
        algorithm: EncryptionAlgorithm,
    ) -> Result<EncryptedData, CryptoError> {
        if key.len() != 32 {
            return Err(CryptoError::InvalidKey);
        }

        let mut nonce_bytes = [0u8; 12];
        rand::thread_rng().fill_bytes(&mut nonce_bytes);
        let nonce = Nonce::from_slice(&nonce_bytes);

        let cipher = Aes256Gcm::new_from_slice(&key)
            .map_err(|_| CryptoError::InvalidKey)?;

        let ciphertext = cipher
            .encrypt(nonce, plaintext.as_ref())
            .map_err(|e| CryptoError::EncryptionFailed {
                reason: e.to_string(),
            })?;

        let (encrypted, tag) = ciphertext.split_at(ciphertext.len() - 16);

        Ok(EncryptedData {
            ciphertext: encrypted.to_vec(),
            nonce: nonce_bytes.to_vec(),
            tag: tag.to_vec(),
            algorithm,
        })
    }

    pub fn decrypt(&self, data: EncryptedData, key: Vec<u8>) -> Result<Vec<u8>, CryptoError> {
        let nonce = Nonce::from_slice(&data.nonce);
        let cipher = Aes256Gcm::new_from_slice(&key)
            .map_err(|_| CryptoError::InvalidKey)?;

        let mut combined = data.ciphertext.clone();
        combined.extend_from_slice(&data.tag);

        cipher
            .decrypt(nonce, combined.as_ref())
            .map_err(|e| CryptoError::DecryptionFailed {
                reason: e.to_string(),
            })
    }

    pub fn hash_password_secure(
        &self,
        password: String,
        iterations: i32,
    ) -> Result<PasswordHashResult, CryptoError> {
        let mut salt = [0u8; 32];
        rand::thread_rng().fill_bytes(&mut salt);
        let salt_hex = hex::encode(salt);

        let mut mac = HmacSha256::new_from_slice(salt.as_ref())
            .map_err(|e| CryptoError::EncryptionFailed {
                reason: e.to_string(),
            })?;

        let effective_iterations = if iterations < 10000 { 10000 } else { iterations };

        for _ in 0..effective_iterations {
            mac.update(password.as_bytes());
        }

        let result = mac.finalize();
        let hash = hex::encode(result.into_bytes());

        Ok(PasswordHashResult {
            hash,
            salt: salt_hex,
            iterations: effective_iterations,
        })
    }

    pub fn verify_password(
        &self,
        password: String,
        stored_hash: PasswordHashResult,
    ) -> Result<bool, CryptoError> {
        let salt = hex::decode(&stored_hash.salt)
            .map_err(|e| CryptoError::InvalidToken {
                reason: format!("Invalid salt: {}", e),
            })?;

        let mut mac = HmacSha256::new_from_slice(salt.as_ref())
            .map_err(|e| CryptoError::EncryptionFailed {
                reason: e.to_string(),
            })?;

        for _ in 0..stored_hash.iterations {
            mac.update(password.as_bytes());
        }

        let result = mac.finalize();
        let hash = hex::encode(result.into_bytes());

        Ok(hash == stored_hash.hash)
    }

    pub fn generate_token(
        &self,
        user_id: String,
        expiry_seconds: i64,
    ) -> Result<String, CryptoError> {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|_| CryptoError::EncryptionFailed {
                reason: "System time error".to_string(),
            })?
            .as_secs();

        let payload = format!(
            "{}:{}:{}",
            user_id,
            now,
            now + expiry_seconds as u64
        );

        let mut mac = HmacSha256::new_from_slice(b"uniffi-token-secret")
            .map_err(|e| CryptoError::EncryptionFailed {
                reason: e.to_string(),
            })?;
        mac.update(payload.as_bytes());
        let signature = hex::encode(mac.finalize().into_bytes());

        Ok(format!("{}.{}", hex::encode(payload), signature))
    }

    pub fn validate_token(&self, token: String) -> Result<TokenPayload, CryptoError> {
        let parts: Vec<&str> = token.split('.').collect();
        if parts.len() != 2 {
            return Err(CryptoError::InvalidToken {
                reason: "Malformed token".to_string(),
            });
        }

        let payload = hex::decode(parts[0])
            .map_err(|e| CryptoError::InvalidToken {
                reason: format!("Invalid payload: {}", e),
            })?;
        let payload_str = String::from_utf8(payload)
            .map_err(|e| CryptoError::InvalidToken {
                reason: format!("Invalid UTF-8: {}", e),
            })?;

        let fields: Vec<&str> = payload_str.split(':').collect();
        if fields.len() != 3 {
            return Err(CryptoError::InvalidToken {
                reason: "Invalid payload structure".to_string(),
            });
        }

        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|_| CryptoError::TokenExpired)?
            .as_secs();

        let expires_at: u64 = fields[2]
            .parse()
            .map_err(|_| CryptoError::InvalidToken {
                reason: "Invalid expiry".to_string(),
            })?;

        if now > expires_at {
            return Err(CryptoError::TokenExpired);
        }

        Ok(TokenPayload {
            user_id: fields[0].to_string(),
            issued_at: fields[1].parse().unwrap_or(0),
            expires_at: expires_at as i64,
            is_valid: true,
        })
    }
}

#[derive(uniffi::Object)]
pub struct AuthService {
    secret_key: String,
    default_token_expiry: i64,
}

#[uniffi::export]
impl AuthService {
    #[uniffi::constructor]
    pub fn new(secret_key: String, default_token_expiry: i64) -> Self {
        Self {
            secret_key,
            default_token_expiry,
        }
    }

    pub fn login(&self, user_id: String, password: String) -> Result<String, CryptoError> {
        let crypto = CryptoService::new();
        let token = crypto.generate_token(user_id, self.default_token_expiry)?;
        Ok(token)
    }

    pub fn verify(&self, token: String) -> Result<TokenPayload, CryptoError> {
        let crypto = CryptoService::new();
        crypto.validate_token(token)
    }

    pub fn revoke_token(&self, _token: String) {
        // In production: add to revocation list / Redis cache
    }
}

#[uniffi::export(callback_interface)]
pub trait ProgressCallback: Send + Sync {
    fn on_progress(&self, current: i32, total: i32);
    fn on_complete(&self, result: String);
}

构建脚本

// build.rs
fn main() {
    uniffi::generate_scaffolding("src/crypto_core.udl").unwrap();
}

模式2:Kotlin绑定与Android集成

生成Kotlin绑定

cargo build --release
cargo run --features=uniffi/cli -- generate \
    --library target/release/libcrypto_core.so \
    --language kotlin \
    --out-dir generated/kotlin

Android项目集成

android-app/
├── app/
│   ├── src/main/java/com/example/crypto/
│   │   ├── CryptoViewModel.kt
│   │   └── MainActivity.kt
│   └── jniLibs/
│       ├── arm64-v8a/libcrypto_core.so
│       ├── armeabi-v7a/libcrypto_core.so
│       └── x86_64/libcrypto_core.so
├── build.gradle.kts
└── local.properties

Kotlin业务代码

package com.example.crypto

import uniffi.crypto_core.*

class CryptoViewModel : ViewModel() {
    private val cryptoService = CryptoService()
    private var authService: AuthService? = null

    private val _encryptState = MutableStateFlow<EncryptUiState>(EncryptUiState.Idle)
    val encryptState: StateFlow<EncryptUiState> = _encryptState.asStateFlow()

    fun initializeAuth(secretKey: String, tokenExpirySeconds: Long) {
        authService = AuthService(secretKey, tokenExpirySeconds)
    }

    fun encryptData(plaintext: ByteArray, key: ByteArray) {
        viewModelScope.launch {
            _encryptState.value = EncryptUiState.Loading
            try {
                val encrypted = cryptoService.encrypt(
                    plaintext = plaintext,
                    key = key,
                    algorithm = EncryptionAlgorithm.AES_256_GCM
                )
                _encryptState.value = EncryptUiState.Success(encrypted)
            } catch (e: CryptoError) {
                _encryptState.value = EncryptUiState.Error(
                    when (e) {
                        is CryptoError.InvalidKey -> "Invalid encryption key"
                        is CryptoError.EncryptionFailed -> "Encryption failed: ${e.reason}"
                        else -> "Unknown error"
                    }
                )
            }
        }
    }

    fun decryptData(encryptedData: EncryptedData, key: ByteArray): ByteArray {
        return try {
            cryptoService.decrypt(encryptedData, key)
        } catch (e: CryptoError.DecryptionFailed) {
            throw CryptoException("Decryption failed: ${e.reason}")
        }
    }

    fun hashPassword(password: String, iterations: Int = 100000): PasswordHashResult {
        return cryptoService.hashPasswordSecure(password, iterations.toLong().toInt())
    }

    fun verifyPassword(password: String, storedHash: PasswordHashResult): Boolean {
        return cryptoService.verifyPassword(password, storedHash)
    }

    fun login(userId: String, password: String): String {
        return authService?.login(userId, password)
            ?: throw IllegalStateException("AuthService not initialized")
    }

    fun verifyToken(token: String): TokenPayload {
        return authService?.verify(token)
            ?: throw IllegalStateException("AuthService not initialized")
    }
}

sealed class EncryptUiState {
    object Idle : EncryptUiState()
    object Loading : EncryptUiState()
    data class Success(val encryptedData: EncryptedData) : EncryptUiState()
    data class Error(val message: String) : EncryptUiState()
}

class CryptoException(message: String) : Exception(message)

Android Activity集成

package com.example.crypto

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import uniffi.crypto_core.*
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    private val viewModel = CryptoViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel.initializeAuth(
            secretKey = "my-secret-key-2026",
            tokenExpirySeconds = 86400L
        )

        lifecycleScope.launch {
            viewModel.encryptState.collect { state ->
                when (state) {
                    is EncryptUiState.Success -> {
                        val base64Ciphertext = android.util.Base64.encodeToString(
                            state.encryptedData.ciphertext.toByteArray(),
                            android.util.Base64.NO_WRAP
                        )
                        println("Encrypted: $base64Ciphertext")
                    }
                    is EncryptUiState.Error -> {
                        println("Error: ${state.message}")
                    }
                    else -> {}
                }
            }
        }

        demonstrateCrypto()
    }

    private fun demonstrateCrypto() {
        val key = ByteArray(32) { (it % 256).toByte() }
        val plaintext = "Hello, UniFFI Cross-Platform!".toByteArray(Charsets.UTF_8)

        viewModel.encryptData(plaintext, key)

        val hashResult = viewModel.hashPassword("user_password_123")
        println("Hash: ${hashResult.hash}")
        println("Salt: ${hashResult.salt}")

        val isValid = viewModel.verifyPassword("user_password_123", hashResult)
        println("Password valid: $isValid")

        val token = viewModel.login("user_001", "password")
        println("Token: $token")

        val payload = viewModel.verifyToken(token)
        println("Token user: ${payload.userId}, valid: ${payload.isValid}")
    }
}

Gradle构建配置

android {
    namespace = "com.example.crypto"
    compileSdk = 36

    defaultConfig {
        applicationId = "com.example.crypto"
        minSdk = 26
        targetSdk = 36
        ndk {
            abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64")
        }
    }

    sourceSets["main"].jniLibs.srcDirs("jniLibs")
}

dependencies {
    implementation("net.java.dev.jna:jna:5.16.0@aar")
}

模式3:Swift绑定与iOS集成

生成Swift绑定

cargo build --target aarch64-apple-ios --release
cargo run --features=uniffi/cli -- generate \
    --library target/aarch64-apple-ios/release/libcrypto_core.a \
    --language swift \
    --out-dir generated/swift

iOS项目结构

ios-app/
├── CryptoApp/
│   ├── CryptoViewModel.swift
│   ├── ContentView.swift
│   └── CryptoApp.swift
├── CryptoCore.xcframework/
│   ├── ios-arm64/
│   └── ios-arm64_x86_64-simulator/
└── Podfile

Swift业务代码

import Foundation
import CryptoCore

@MainActor
class CryptoViewModel: ObservableObject {
    private let cryptoService = CryptoService()
    private var authService: AuthService?

    @Published var encryptedData: EncryptedData?
    @Published var errorMessage: String?
    @Published var isLoading = false
    @Published var tokenPayload: TokenPayload?

    func initializeAuth(secretKey: String, tokenExpirySeconds: Int64) {
        authService = AuthService(secretKey: secretKey, defaultTokenExpiry: tokenExpirySeconds)
    }

    func encryptData(plaintext: Data, key: Data) {
        isLoading = true
        errorMessage = nil

        do {
            let encrypted = try cryptoService.encrypt(
                plaintext: plaintext,
                key: key,
                algorithm: .aes256Gcm
            )
            encryptedData = encrypted
            isLoading = false
        } catch let error as CryptoError {
            switch error {
            case .invalidKey:
                errorMessage = "Invalid encryption key"
            case .encryptionFailed(let reason):
                errorMessage = "Encryption failed: \(reason)"
            default:
                errorMessage = "Unknown error"
            }
            isLoading = false
        } catch {
            errorMessage = error.localizedDescription
            isLoading = false
        }
    }

    func decryptData(encryptedData: EncryptedData, key: Data) -> Data? {
        do {
            return try cryptoService.decrypt(data: encryptedData, key: key)
        } catch let error as CryptoError {
            if case .decryptionFailed(let reason) = error {
                errorMessage = "Decryption failed: \(reason)"
            }
            return nil
        } catch {
            errorMessage = error.localizedDescription
            return nil
        }
    }

    func hashPassword(password: String, iterations: Int32 = 100000) -> PasswordHashResult? {
        do {
            return try cryptoService.hashPasswordSecure(
                password: password,
                iterations: iterations
            )
        } catch {
            errorMessage = error.localizedDescription
            return nil
        }
    }

    func verifyPassword(password: String, storedHash: PasswordHashResult) -> Bool {
        do {
            return try cryptoService.verifyPassword(password: password, storedHash: storedHash)
        } catch {
            return false
        }
    }

    func login(userId: String, password: String) -> String? {
        do {
            return try authService?.login(userId: userId, password: password)
        } catch {
            errorMessage = error.localizedDescription
            return nil
        }
    }

    func verifyToken(token: String) {
        do {
            tokenPayload = try authService?.verify(token: token)
        } catch {
            errorMessage = error.localizedDescription
        }
    }
}

SwiftUI视图

import SwiftUI
import CryptoCore

struct ContentView: View {
    @StateObject private var viewModel = CryptoViewModel()

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                if viewModel.isLoading {
                    ProgressView("Encrypting...")
                }

                if let error = viewModel.errorMessage {
                    Text(error)
                        .foregroundColor(.red)
                        .font(.caption)
                }

                Button("Encrypt Demo Data") {
                    let key = Data((0..<32).map { UInt8($0 % 256) })
                    let plaintext = "Hello, UniFFI iOS!".data(using: .utf8)!
                    viewModel.encryptData(plaintext: plaintext, key: key)
                }
                .buttonStyle(.borderedProminent)

                if let encrypted = viewModel.encryptedData {
                    VStack(alignment: .leading) {
                        Text("Ciphertext (Base64):")
                            .font(.caption)
                            .foregroundColor(.secondary)
                        Text(encrypted.ciphertext.base64EncodedString())
                            .font(.system(.caption, design: .monospaced))
                            .lineLimit(3)
                    }
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(8)
                }

                Button("Login Demo") {
                    viewModel.initializeAuth(
                        secretKey: "my-secret-key-2026",
                        tokenExpirySeconds: 86400
                    )
                    if let token = viewModel.login(userId: "user_001", password: "pass") {
                        viewModel.verifyToken(token: token)
                    }
                }
                .buttonStyle(.bordered)

                if let payload = viewModel.tokenPayload {
                    VStack(alignment: .leading) {
                        Text("Token User: \(payload.userId)")
                        Text("Valid: \(payload.isValid ? "Yes" : "No")")
                        Text("Expires: \(Date(timeIntervalSince1970: TimeInterval(payload.expiresAt)).formatted())")
                    }
                    .font(.caption)
                    .padding()
                    .background(Color.green.opacity(0.1))
                    .cornerRadius(8)
                }
            }
            .padding()
            .navigationTitle("CryptoCore Demo")
        }
    }
}

XCFramework构建脚本

#!/bin/bash
set -e

RUST_TARGETS=("aarch64-apple-ios" "aarch64-apple-ios-sim" "x86_64-apple-ios")
LIB_NAME="libcrypto_core.a"

for target in "${RUST_TARGETS[@]}"; do
    rustup target add "$target"
    cargo build --target "$target" --release
done

mkdir -p build/xcframework

xcodebuild -create-xcframework \
    -library "target/aarch64-apple-ios/release/$LIB_NAME" \
    -headers generated/swift \
    -library "target/aarch64-apple-ios-sim/release/$LIB_NAME" \
    -headers generated/swift \
    -output "build/xcframework/CryptoCore.xcframework"

模式4:Python绑定与桌面应用

生成Python绑定

cargo build --release
cargo run --features=uniffi/cli -- generate \
    --library target/release/libcrypto_core.so \
    --language python \
    --out-dir generated/python

Python业务代码

from generated.python import crypto_core
import base64
import os


class CryptoManager:
    def __init__(self, secret_key: str = "my-secret-key-2026", token_expiry: int = 86400):
        self.crypto_service = crypto_core.CryptoService()
        self.auth_service = crypto_core.AuthService(secret_key, token_expiry)

    def encrypt_data(self, plaintext: str, key: bytes = None) -> crypto_core.EncryptedData:
        if key is None:
            key = os.urandom(32)

        if len(key) != 32:
            raise ValueError("Key must be 32 bytes for AES-256")

        plaintext_bytes = plaintext.encode("utf-8")
        encrypted = self.crypto_service.encrypt(
            plaintext=plaintext_bytes,
            key=key,
            algorithm=crypto_core.EncryptionAlgorithm.AES_256_GCM,
        )

        print(f"Ciphertext (Base64): {base64.b64encode(encrypted.ciphertext).decode()}")
        print(f"Nonce (Base64): {base64.b64encode(encrypted.nonce).decode()}")
        print(f"Algorithm: {encrypted.algorithm}")

        return encrypted

    def decrypt_data(self, encrypted: crypto_core.EncryptedData, key: bytes) -> str:
        plaintext_bytes = self.crypto_service.decrypt(encrypted, key)
        return plaintext_bytes.decode("utf-8")

    def hash_password(self, password: str, iterations: int = 100000) -> crypto_core.PasswordHashResult:
        result = self.crypto_service.hash_password_secure(password, iterations)
        print(f"Hash: {result.hash}")
        print(f"Salt: {result.salt}")
        print(f"Iterations: {result.iterations}")
        return result

    def verify_password(self, password: str, stored_hash: crypto_core.PasswordHashResult) -> bool:
        return self.crypto_service.verify_password(password, stored_hash)

    def login(self, user_id: str, password: str) -> str:
        token = self.auth_service.login(user_id, password)
        print(f"Token: {token}")
        return token

    def verify_token(self, token: str) -> crypto_core.TokenPayload:
        payload = self.auth_service.verify(token)
        print(f"User: {payload.user_id}")
        print(f"Valid: {payload.is_valid}")
        print(f"Expires: {payload.expires_at}")
        return payload


def main():
    manager = CryptoManager()

    key = os.urandom(32)
    encrypted = manager.encrypt_data("Hello, UniFFI Python!", key)

    decrypted = manager.decrypt_data(encrypted, key)
    print(f"Decrypted: {decrypted}")

    hash_result = manager.hash_password("my_secure_password")
    is_valid = manager.verify_password("my_secure_password", hash_result)
    print(f"Password valid: {is_valid}")

    is_invalid = manager.verify_password("wrong_password", hash_result)
    print(f"Wrong password valid: {is_invalid}")

    token = manager.login("user_001", "password123")
    payload = manager.verify_token(token)

    try:
        manager.encrypt_data("test", key=b"short")
    except crypto_core.CryptoError.InvalidKey:
        print("Caught: InvalidKey error as expected")


if __name__ == "__main__":
    main()

Python异步批量处理

import asyncio
from concurrent.futures import ThreadPoolExecutor
from generated.python import crypto_core


class AsyncCryptoManager:
    def __init__(self, worker_count: int = 4):
        self.crypto_service = crypto_core.CryptoService()
        self.executor = ThreadPoolExecutor(max_workers=worker_count)

    async def encrypt_batch(self, items: list[tuple[str, bytes]]) -> list[crypto_core.EncryptedData]:
        loop = asyncio.get_event_loop()
        tasks = []
        for plaintext, key in items:
            task = loop.run_in_executor(
                self.executor,
                self._encrypt_sync,
                plaintext.encode("utf-8"),
                key,
            )
            tasks.append(task)
        return await asyncio.gather(*tasks)

    def _encrypt_sync(self, plaintext: bytes, key: bytes) -> crypto_core.EncryptedData:
        return self.crypto_service.encrypt(
            plaintext=plaintext,
            key=key,
            algorithm=crypto_core.EncryptionAlgorithm.AES_256_GCM,
        )

    async def hash_passwords_batch(self, passwords: list[str], iterations: int = 100000) -> list[crypto_core.PasswordHashResult]:
        loop = asyncio.get_event_loop()
        tasks = []
        for password in passwords:
            task = loop.run_in_executor(
                self.executor,
                self.crypto_service.hash_password_secure,
                password,
                iterations,
            )
            tasks.append(task)
        return await asyncio.gather(*tasks)

    def shutdown(self):
        self.executor.shutdown(wait=True)

模式5:异步操作与回调

UniFFI的异步支持通过回调接口实现,避免阻塞宿主语言的主线程。

Rust异步任务

use std::sync::Arc;
use std::time::Duration;

#[derive(uniffi::Object)]
pub struct BatchProcessor {
    crypto: Arc<CryptoService>,
}

#[uniffi::export]
impl BatchProcessor {
    #[uniffi::constructor]
    pub fn new() -> Self {
        Self {
            crypto: Arc::new(CryptoService::new()),
        }
    }

    pub fn process_batch(
        &self,
        items: Vec<Vec<u8>>,
        key: Vec<u8>,
        callback: Box<dyn ProgressCallback>,
    ) -> Result<Vec<EncryptedData>, CryptoError> {
        let total = items.len();
        let mut results = Vec::with_capacity(total);

        for (index, plaintext) in items.iter().enumerate() {
            let encrypted = self.crypto.encrypt(
                plaintext.clone(),
                key.clone(),
                EncryptionAlgorithm::Aes256Gcm,
            )?;

            results.push(encrypted);
            callback.on_progress((index + 1) as i32, total as i32);

            if index > 0 && index % 100 == 0 {
                std::thread::sleep(Duration::from_millis(10));
            }
        }

        callback.on_complete(format!("Processed {} items", total));
        Ok(results)
    }

    pub fn benchmark_hash(
        &self,
        password: String,
        iterations: i32,
        rounds: i32,
        callback: Box<dyn ProgressCallback>,
    ) -> Result<String, CryptoError> {
        let mut results = Vec::new();

        for i in 0..rounds {
            let start = std::time::Instant::now();
            self.crypto.hash_password_secure(password.clone(), iterations)?;
            let elapsed = start.elapsed().as_millis();
            results.push(elapsed);

            callback.on_progress(i + 1, rounds);
        }

        let avg: f64 = results.iter().sum::<u128>() as f64 / results.len() as f64;
        let summary = format!(
            "Average: {:.2}ms, Min: {}ms, Max: {}ms",
            avg,
            results.iter().min().unwrap_or(&0),
            results.iter().max().unwrap_or(&0),
        );

        callback.on_complete(summary.clone());
        Ok(summary)
    }
}

Kotlin回调实现

class BatchEncryptCallback : ProgressCallback {
    override fun onProgress(current: Int, total: Int) {
        val percent = (current * 100) / total
        println("Progress: $percent% ($current/$total)")
    }

    override fun onComplete(result: String) {
        println("Complete: $result")
    }
}

fun performBatchEncryption() {
    val processor = BatchProcessor()
    val key = ByteArray(32) { (it % 256).toByte() }
    val items = (1..500).map { "Item $it data payload".toByteArray() }

    CoroutineScope(Dispatchers.IO).launch {
        try {
            val results = processor.processBatch(items, key, BatchEncryptCallback())
            println("Encrypted ${results.size} items")
        } catch (e: CryptoError) {
            println("Batch error: $e")
        }
    }
}

Swift回调实现

class BatchEncryptCallback: ProgressCallback {
    func onProgress(current: Int32, total: Int32) {
        let percent = Int(current) * 100 / Int(total)
        print("Progress: \(percent)% (\(current)/\(total))")
    }

    func onComplete(result: String) {
        print("Complete: \(result)")
    }
}

func performBatchEncryption() {
    let processor = BatchProcessor()
    let key = Data((0..<32).map { UInt8($0 % 256) })
    let items: [Data] = (1...500).map { "Item \($0) data payload".data(using: .utf8)! }

    DispatchQueue.global(qos: .userInitiated).async {
        do {
            let results = try processor.processBatch(
                items: items,
                key: key,
                callback: BatchEncryptCallback()
            )
            print("Encrypted \(results.count) items")
        } catch {
            print("Batch error: \(error)")
        }
    }
}

Python回调实现

class BatchEncryptCallback(crypto_core.ProgressCallback):
    def on_progress(self, current: int, total: int):
        percent = (current * 100) // total
        print(f"Progress: {percent}% ({current}/{total})")

    def on_complete(self, result: str):
        print(f"Complete: {result}")


def perform_batch_encryption():
    processor = crypto_core.BatchProcessor()
    key = os.urandom(32)
    items = [f"Item {i} data payload".encode("utf-8") for i in range(1, 501)]

    results = processor.process_batch(items, key, BatchEncryptCallback())
    print(f"Encrypted {len(results)} items")

模式6:错误处理与类型转换

类型映射表

Rust类型 Kotlin类型 Swift类型 Python类型
i32 Int Int32 int
i64 Long Int64 int
f64 Double Double float
bool Boolean Bool bool
String String String str
Vec ByteArray Data bytes
Vec List [T] list[T]
Option T? T? Optional[T]
Result<T, E> T (throws E) T (throws E) T (raises E)
enum sealed class enum Enum
struct (Record) data class struct dataclass

Rust错误枚举

#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum DataStoreError {
    #[error("Connection failed: {reason}")]
    ConnectionFailed { reason: String },

    #[error("Record not found: {id}")]
    NotFound { id: String },

    #[error("Validation error: {field} - {message}")]
    ValidationError { field: String, message: String },

    #[error("Permission denied")]
    PermissionDenied,

    #[error("Rate limit exceeded, retry after {seconds}s")]
    RateLimitExceeded { seconds: i64 },
}

#[derive(Debug, uniffi::Record)]
pub struct UserRecord {
    pub id: String,
    pub name: String,
    pub email: String,
    pub role: UserRole,
    pub created_at: i64,
    pub updated_at: Option<i64>,
}

#[derive(Debug, uniffi::Enum)]
pub enum UserRole {
    Admin,
    Editor,
    Viewer,
}

#[derive(uniffi::Object)]
pub struct DataStore {
    connection_string: String,
}

#[uniffi::export]
impl DataStore {
    #[uniffi::constructor]
    pub fn new(connection_string: String) -> Result<Self, DataStoreError> {
        if connection_string.is_empty() {
            return Err(DataStoreError::ConnectionFailed {
                reason: "Empty connection string".to_string(),
            });
        }
        Ok(Self { connection_string })
    }

    pub fn get_user(&self, id: String) -> Result<UserRecord, DataStoreError> {
        if id.is_empty() {
            return Err(DataStoreError::ValidationError {
                field: "id".to_string(),
                message: "ID cannot be empty".to_string(),
            });
        }

        Ok(UserRecord {
            id: id.clone(),
            name: format!("User {}", id),
            email: format!("user{}@example.com", id),
            role: UserRole::Viewer,
            created_at: 1718500000,
            updated_at: None,
        })
    }

    pub fn update_user(&self, id: String, name: String, email: String) -> Result<UserRecord, DataStoreError> {
        if name.is_empty() {
            return Err(DataStoreError::ValidationError {
                field: "name".to_string(),
                message: "Name cannot be empty".to_string(),
            });
        }

        if !email.contains('@') {
            return Err(DataStoreError::ValidationError {
                field: "email".to_string(),
                message: "Invalid email format".to_string(),
            });
        }

        Ok(UserRecord {
            id,
            name,
            email,
            role: UserRole::Editor,
            created_at: 1718500000,
            updated_at: Some(1718600000),
        })
    }
}

Kotlin错误处理

fun handleDataStoreOperations() {
    val store = try {
        DataStore("postgresql://localhost/mydb")
    } catch (e: DataStoreError.ConnectionFailed) {
        println("Connection failed: ${e.reason}")
        return
    }

    try {
        val user = store.getUser("user_001")
        println("User: ${user.name}, Role: ${user.role}")
    } catch (e: DataStoreError.NotFound) {
        println("User not found: ${e.id}")
    } catch (e: DataStoreError.ValidationError) {
        println("Validation: ${e.field} - ${e.message}")
    }

    try {
        val updated = store.updateUser("user_001", "New Name", "new@example.com")
        println("Updated: ${updated.name}")
    } catch (e: DataStoreError) {
        when (e) {
            is DataStoreError.ValidationError -> println("Invalid: ${e.field}")
            is DataStoreError.PermissionDenied -> println("No permission")
            is DataStoreError.RateLimitExceeded -> println("Retry after ${e.seconds}s")
            else -> println("Error: $e")
        }
    }
}

Swift错误处理

func handleDataStoreOperations() {
    let store: DataStore
    do {
        store = try DataStore(connectionString: "postgresql://localhost/mydb")
    } catch let error as DataStoreError.ConnectionFailed {
        print("Connection failed: \(error.reason)")
        return
    } catch {
        print("Unexpected error: \(error)")
        return
    }

    do {
        let user = try store.getUser(id: "user_001")
        print("User: \(user.name), Role: \(user.role)")
    } catch let error as DataStoreError.NotFound {
        print("User not found: \(error.id)")
    } catch let error as DataStoreError.ValidationError {
        print("Validation: \(error.field) - \(error.message)")
    }

    do {
        let updated = try store.updateUser(id: "user_001", name: "New Name", email: "new@example.com")
        print("Updated: \(updated.name)")
    } catch let error as DataStoreError {
        switch error {
        case .validationError(let field, let message):
            print("Invalid: \(field) - \(message)")
        case .permissionDenied:
            print("No permission")
        case .rateLimitExceeded(let seconds):
            print("Retry after \(seconds)s")
        default:
            print("Error: \(error)")
        }
    }
}

5个常见坑及解决方案

坑1:UDL和Rust代码不同步

// ❌ 错误:UDL定义了3个字段,Rust struct只有2个
// UDL: dictionary User { string id; string name; string email; }
// Rust:
#[derive(uniffi::Record)]
pub struct User {
    pub id: String,
    pub name: String,
    // 缺少 email 字段!
}

// ✅ 正确:使用proc-macro代替UDL,让Rust代码即接口
// 不需要单独维护UDL文件
#[derive(uniffi::Record)]
pub struct User {
    pub id: String,
    pub name: String,
    pub email: String,
}

坑2:Android加载动态库失败

// ❌ 错误:直接使用System.loadLibrary
System.loadLibrary("crypto_core") // 找不到库

// ✅ 正确:UniFFI使用JNA,确保JNA依赖正确
// build.gradle.kts:
dependencies {
    implementation("net.java.dev.jna:jna:5.16.0@aar")
}
// 确保so文件放在正确的jniLibs目录下
// jniLibs/arm64-v8a/libcrypto_core.so

坑3:iOS静态库链接顺序

# ❌ 错误:链接顺序导致符号未定义
# Other Linker Flags: -lcrypto_core

# ✅ 正确:确保链接顺序和框架依赖正确
# Other Linker Flags:
#   -lcrypto_core
#   -framework Security
#   -framework Foundation
# Framework Search Paths 添加 XCFramework 路径

坑4:回调在主线程阻塞

// ❌ 错误:在主线程调用耗时Rust函数
fun onButtonClick() {
    val result = cryptoService.hashPasswordSecure("password", 100000)
    // ANR! 主线程被阻塞
}

// ✅ 正确:在IO线程执行
fun onButtonClick() {
    CoroutineScope(Dispatchers.IO).launch {
        val result = cryptoService.hashPasswordSecure("password", 100000)
        withContext(Dispatchers.Main) {
            updateUI(result)
        }
    }
}

坑5:Vec和String的编码问题

# ❌ 错误:Python直接传str给需要bytes的参数
encrypted = crypto_service.encrypt(
    plaintext="Hello",  # 类型错误!期望bytes
    key=key_bytes,
    algorithm=crypto_core.EncryptionAlgorithm.AES_256_GCM,
)

# ✅ 正确:显式编码
encrypted = crypto_service.encrypt(
    plaintext="Hello".encode("utf-8"),  # bytes
    key=key_bytes,
    algorithm=crypto_core.EncryptionAlgorithm.AES_256_GCM,
)

10个常见报错排查

序号 报错信息 原因 解决方法
1 failed to load library: libcrypto_core.so not found Android未正确放置so文件 检查jniLibs目录结构和ABI过滤
2 UniffiInternalError: pointer is null Rust返回None/空对象 检查Rust构造函数是否返回了Ok值
3 TypeMismatch: expected bytes, got str Python传str给bytes参数 使用.encode("utf-8")转换
4 Undefined symbol: _uniffi_crypto_core_fn_init iOS链接顺序错误 检查Other Linker Flags和链接顺序
5 UDL parse error: unknown type UDL引用了未定义的类型 确保所有类型在UDL中先定义再使用
6 cargo build error: duplicate lang item uniffi版本冲突 统一uniffi依赖版本,检查Cargo.toml
7 JNA: java.lang.UnsatisfiedLinkError JNA版本与so文件不兼容 更新JNA版本,确保与编译目标匹配
8 Callback dropped: on_progress not called 回调对象被GC回收 在Kotlin/Swift中保持回调对象引用
9 cannot find type CryptoError in scope Swift未导入绑定模块 添加import CryptoCore
10 thread 'main' panicked at 'assertion failed: key.len() == 32' 密钥长度不匹配 确保AES-256使用32字节密钥

进阶优化技巧

1. 使用Proc-Macro替代UDL

// 不再需要单独的UDL文件,直接用Rust属性宏定义接口
use uniffi;

#[derive(uniffi::Record)]
pub struct AppConfig {
    pub api_endpoint: String,
    pub timeout_seconds: i64,
    pub max_retries: i32,
    pub enable_logging: bool,
}

#[derive(uniffi::Enum)]
pub enum ConnectionStatus {
    Connected,
    Disconnected { reason: String },
    Reconnecting { attempt: i32 },
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum NetworkError {
    #[error("Timeout after {seconds}s")]
    Timeout { seconds: i64 },
    #[error("Connection refused: {host}")]
    ConnectionRefused { host: String },
}

#[derive(uniffi::Object)]
pub struct NetworkClient {
    config: AppConfig,
    status: std::sync::Mutex<ConnectionStatus>,
}

#[uniffi::export]
impl NetworkClient {
    #[uniffi::constructor]
    pub fn new(config: AppConfig) -> Result<Self, NetworkError> {
        if config.api_endpoint.is_empty() {
            return Err(NetworkError::ConnectionRefused {
                host: "empty endpoint".to_string(),
            });
        }
        Ok(Self {
            config,
            status: std::sync::Mutex::new(ConnectionStatus::Disconnected {
                reason: "Not initialized".to_string(),
            }),
        })
    }

    pub fn connect(&self) -> Result<(), NetworkError> {
        let mut status = self.status.lock().unwrap();
        *status = ConnectionStatus::Connected;
        Ok(())
    }

    pub fn get_status(&self) -> ConnectionStatus {
        self.status.lock().unwrap().clone()
    }
}

2. 跨平台CI/CD构建

name: Build Cross-Platform Bindings

on: [push, pull_request]

jobs:
  build-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android
      - name: Install cargo-ndk
        run: cargo install cargo-ndk
      - name: Build Android
        run: cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 build --release
      - name: Generate Kotlin bindings
        run: cargo run --features=uniffi/cli -- generate --library target/aarch64-linux-android/release/libcrypto_core.so --language kotlin --out-dir dist/kotlin
      - uses: actions/upload-artifact@v4
        with:
          name: android-bindings
          path: dist/

  build-ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-apple-ios,x86_64-apple-ios,aarch64-apple-ios-sim
      - name: Build iOS
        run: |
          cargo build --target aarch64-apple-ios --release
          cargo build --target aarch64-apple-ios-sim --release
      - name: Generate Swift bindings
        run: cargo run --features=uniffi/cli -- generate --library target/aarch64-apple-ios/release/libcrypto_core.a --language swift --out-dir dist/swift
      - name: Create XCFramework
        run: |
          xcodebuild -create-xcframework \
            -library target/aarch64-apple-ios/release/libcrypto_core.a \
            -headers dist/swift \
            -library target/aarch64-apple-ios-sim/release/libcrypto_core.a \
            -headers dist/swift \
            -output dist/CryptoCore.xcframework
      - uses: actions/upload-artifact@v4
        with:
          name: ios-bindings
          path: dist/

  build-python:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - name: Build and generate Python bindings
        run: |
          cargo build --release
          cargo run --features=uniffi/cli -- generate --library target/release/libcrypto_core.so --language python --out-dir dist/python
      - uses: actions/upload-artifact@v4
        with:
          name: python-bindings
          path: dist/

3. 对象生命周期管理

use std::sync::{Arc, Mutex};

#[derive(uniffi::Object)]
pub struct SessionManager {
    sessions: Mutex<std::collections::HashMap<String, Arc<UserSession>>>,
    max_sessions: usize,
}

#[derive(uniffi::Object)]
pub struct UserSession {
    pub id: String,
    pub user_id: String,
    created_at: i64,
    last_active: Mutex<i64>,
}

#[uniffi::export]
impl SessionManager {
    #[uniffi::constructor]
    pub fn new(max_sessions: i32) -> Self {
        Self {
            sessions: Mutex::new(std::collections::HashMap::new()),
            max_sessions: max_sessions as usize,
        }
    }

    pub fn create_session(&self, user_id: String) -> Result<Arc<UserSession>, CryptoError> {
        let mut sessions = self.sessions.lock().unwrap();

        if sessions.len() >= self.max_sessions {
            let oldest = sessions
                .iter()
                .min_by_key(|(_, s)| s.last_active.lock().unwrap().clone())
                .map(|k| k.0.clone());
            if let Some(key) = oldest {
                sessions.remove(&key);
            }
        }

        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;

        let session = Arc::new(UserSession {
            id: uuid::Uuid::new_v4().to_string(),
            user_id,
            created_at: now,
            last_active: Mutex::new(now),
        });

        sessions.insert(session.id.clone(), Arc::clone(&session));
        Ok(session)
    }

    pub fn get_session(&self, id: String) -> Option<Arc<UserSession>> {
        let sessions = self.sessions.lock().unwrap();
        sessions.get(&id).cloned()
    }

    pub fn active_count(&self) -> i32 {
        self.sessions.lock().unwrap().len() as i32
    }
}

对比分析:UniFFI vs JNI vs FFI vs CXX

维度 UniFFI JNI Raw FFI CXX
目标语言 Kotlin/Swift/Python/Ruby等7+ 仅Java/Kotlin 任何支持C ABI的语言 C++
接口定义 UDL或proc-macro Java native方法 C header + extern cxx::bridge宏
代码生成 自动生成绑定 手动或javah 手动 自动生成
类型安全 编译时检查 运行时 运行时 编译时检查
异步支持 回调接口 手动管理 手动管理 有限支持
错误映射 自动映射到宿主异常 手动检查 手动检查 Result映射
学习曲线 ⭐中 ⭐陡 ⭐极陡 ⭐中
移动端支持 Android+iOS 仅Android 需要额外封装 需要额外封装
维护成本 ⭐低 ⭐高 ⭐极高 ⭐中
生产验证 Firefox/Application Services Android生态 广泛 Chromium/Fuchsia
社区活跃度 ⭐高 ⭐极高 ⭐中 ⭐高

选择建议

决策流程:
1. 只需要Java/Kotlin绑定? → JNI(成熟但繁琐)
2. 需要多语言绑定? → UniFFI(首选)
3. 需要C++互操作? → CXX
4. 需要极致控制? → Raw FFI(不推荐除非必要)
5. 移动端跨平台? → UniFFI(Android+iOS一站式)

在线工具推荐


相关阅读


总结:Rust UniFFI跨平台开发的核心价值是Write Once, Bind Everywhere——用Rust写核心逻辑,自动生成Kotlin/Swift/Python绑定。2026年的生产实践:用proc-macro替代UDL简化维护→Kotlin JNA集成Android→XCFramework集成iOS→ThreadPoolExecutor处理Python异步→回调接口避免主线程阻塞→统一错误映射保证一致性。关键是要理解Lift/Lower的类型转换机制,这是UniFFI跨语言通信的根基。Mozilla Firefox已经证明这条路走得通,你的项目也可以。

本站提供浏览器本地工具,免注册即可试用 →

#Rust#UniFFI#跨平台#多语言绑定#Kotlin#Swift#2026#编程语言