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-CN/json/format — 调试UniFFI生成的JSON配置
- Base64编解码:/zh-CN/encode/base64 — 处理加密数据的Base64编码
- 代码格式化:/zh-CN/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已经证明这条路走得通,你的项目也可以。
本站提供浏览器本地工具,免注册即可试用 →