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#编程语言