Rust UniFFIクロスプラットフォーム開発:コアロジックから多言語バインディングまでの6つのプロダクションパターン

编程语言

3つの言語で同じロジックを書いて、まだ正気ですか

暗号化モジュールをRustで書き、Android向けにKotlinで書き直し、iOS向けにSwiftでさらに書き直し、Pythonサーバーでもう一度書く。4つのコードベース、4つのバグセット、4回のセキュリティ監査。さらに絶望的なのは:Kotlin版のAESとSwift版のAESの動作が一致しない——3日間デバッグして、パディングモードの違いだと判明した。

2026年、Rust UniFFIが究極の答えを出す:コアロジックをRustで1回書けば、Kotlin/Swift/Pythonバインディングを自動生成。Mozilla製、FirefoxとApplication Servicesがプロダクション環境で3年間稼働中。これはおもちゃプロジェクトではない、クロスプラットフォーム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バージョン競合 Cargo.tomlでUniFFI依存バージョンを統一
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フォーマッター:/ja/json/format — UniFFI生成JSON設定のデバッグ
  • Base64エンコード/デコード:/ja/encode/base64 — 暗号化データのBase64エンコーディング処理
  • コードフォーマッター:/ja/dev/code-formatter — 生成されたバインディングコードのフォーマット

関連記事


まとめ: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#编程语言