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透過靜態庫連結
目錄
- UniFFI核心概念
- 模式1:UDL介面定義與型別系統
- 模式2:Kotlin繫結與Android整合
- 模式3:Swift繫結與iOS整合
- 模式4:Python繫結與桌面應用
- 模式5:非同步操作與回呼
- 模式6:錯誤處理與型別轉換
- 5個常見坑及解決方案
- 10個常見報錯排查
- 進階最佳化技巧
- 對比分析:UniFFI vs JNI vs FFI vs CXX
- 線上工具推薦
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一站式)
線上工具推薦
- JSON格式化:/zh-TW/json/format — 偵錯UniFFI生成的JSON配置
- Base64編解碼:/zh-TW/encode/base64 — 處理加密資料的Base64編碼
- 程式碼格式化:/zh-TW/dev/code-formatter — 格式化生成的繫結程式碼
相關閱讀
- Rust Axum Web框架:從路由設計到中介軟體的5個生產級實戰模式 — Axum框架的生產級實踐
- Rust嵌入式Linux開發:從交叉編譯到部署的完整指南 — Rust在嵌入式場景的應用
- Rust Tokio Channel模式:5種生產級併發通訊方案 — Tokio非同步通道實戰
總結: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已經證明這條路走得通,你的專案也可以。
本站提供瀏覽器本地工具,免註冊即可試用 →