Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

直接 FFI vs flutter_rust_bridge 詳細比較

核心差異概覽

特性直接 FFIflutter_rust_bridge
代碼生成手動編寫所有綁定自動生成綁定代碼
開發速度慢 (需手寫大量樣板)快 (自動化)
學習曲線陡峭 (需深度理解 FFI)平緩 (抽象化細節)
性能最佳 (零抽象開銷)微小開銷 (包裝層)
類型安全手動保證自動保證
記憶體管理完全手動部分自動化
錯誤處理手動實現內建 Result 轉換
維護成本高 (手動同步)低 (自動同步)

1. 代碼編寫差異

直接 FFI 方式

Rust 端 (需要手動 C ABI)

#![allow(unused)]
fn main() {
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

// 手動定義 C-compatible 結構
#[repr(C)]
pub struct DeviceInfo {
    pub index: u32,
    pub name: *mut c_char,
    pub connected: bool,
}

// 手動處理字串轉換和記憶體管理
#[no_mangle]
pub extern "C" fn create_client(name: *const c_char) -> u64 {
    let c_str = unsafe { CStr::from_ptr(name) };
    let name_str = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => return 0, // 錯誤處理複雜
    };
    
    // 實際邏輯...
    123 // 假設的客戶端 ID
}

// 手動處理複雜返回類型
#[no_mangle]
pub extern "C" fn get_devices(client_id: u64, count: *mut u32) -> *mut DeviceInfo {
    // 手動分配記憶體
    // 手動填充結構
    // 複雜的錯誤處理...
    std::ptr::null_mut()
}

// 必須手動提供記憶體釋放函數
#[no_mangle]
pub extern "C" fn free_device_info(ptr: *mut DeviceInfo, count: u32) {
    // 手動釋放記憶體...
}
}

Dart 端 (需要手動 FFI 綁定)

import 'dart:ffi';
import 'package:ffi/ffi.dart';

// 手動定義 C 結構對應
class DeviceInfo extends Struct {
  @Uint32()
  external int index;
  
  external Pointer<Utf8> name;
  
  @Bool()
  external bool connected;
}

// 手動定義函數簽名
typedef CreateClientNative = Uint64 Function(Pointer<Utf8>);
typedef CreateClient = int Function(Pointer<Utf8>);

typedef GetDevicesNative = Pointer<DeviceInfo> Function(Uint64, Pointer<Uint32>);
typedef GetDevices = Pointer<DeviceInfo> Function(int, Pointer<Uint32>);

class ButtplugFFI {
  late final CreateClient _createClient;
  late final GetDevices _getDevices;
  
  ButtplugFFI() {
    final lib = DynamicLibrary.open('libbuttplug.so');
    _createClient = lib.lookupFunction<CreateClientNative, CreateClient>('create_client');
    _getDevices = lib.lookupFunction<GetDevicesNative, GetDevices>('get_devices');
  }
  
  // 手動處理類型轉換和記憶體管理
  int createClient(String name) {
    final namePtr = name.toNativeUtf8();
    try {
      return _createClient(namePtr);
    } finally {
      malloc.free(namePtr); // 手動釋放
    }
  }
  
  List<Map<String, dynamic>> getDevices(int clientId) {
    final countPtr = malloc<Uint32>();
    try {
      final devicesPtr = _getDevices(clientId, countPtr);
      final count = countPtr.value;
      
      final devices = <Map<String, dynamic>>[];
      for (int i = 0; i < count; i++) {
        final device = devicesPtr.elementAt(i).ref;
        devices.add({
          'index': device.index,
          'name': device.name.toDartString(),
          'connected': device.connected,
        });
      }
      
      // 手動釋放複雜記憶體結構
      _freeDeviceInfo(devicesPtr, count);
      return devices;
    } finally {
      malloc.free(countPtr);
    }
  }
}

flutter_rust_bridge 方式

Rust 端 (使用原生 Rust 類型)

#![allow(unused)]
fn main() {
// 直接使用 Rust 原生類型,無需 C ABI
#[derive(Clone)]
pub struct DeviceInfo {
    pub index: u32,
    pub name: String,      // 直接用 String!
    pub connected: bool,
}

pub struct ButtplugClient {
    // 內部實現...
}

// 直接返回 Result,自動轉換為 Dart 異常
pub fn create_client(name: String) -> Result<ButtplugClient, String> {
    // 直接使用 Rust String,無需手動轉換
    if name.is_empty() {
        return Err("Name cannot be empty".to_string());
    }
    
    // 實際實現...
    Ok(ButtplugClient { /* ... */ })
}

// 直接返回 Vec,自動轉換為 Dart List
pub fn get_devices(client: &ButtplugClient) -> Result<Vec<DeviceInfo>, String> {
    // 返回原生 Rust 類型
    Ok(vec![
        DeviceInfo {
            index: 1,
            name: "Lovense Device".to_string(),
            connected: true,
        }
    ])
}

// 異步支持!
pub async fn start_scanning(client: &ButtplugClient) -> Result<(), String> {
    // 直接使用 async/await
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
    Ok(())
}
}

Dart 端 (自動生成)

// 這些代碼是自動生成的!
import 'bridge_generated.dart';

class DeviceInfo {
  final int index;
  final String name;      // 直接是 String!
  final bool connected;
  
  DeviceInfo({required this.index, required this.name, required this.connected});
}

class ButtplugBridge {
  static late final _instance = ButtplugBridgeImpl.init(
    ExternalLibrary.open('libbuttplug.so')
  );
  
  // 自動生成的方法,類型安全
  static Future<ButtplugClient> createClient({required String name}) async {
    return await _instance.createClient(name: name);
  }
  
  // 自動處理 Result -> Exception 轉換
  static Future<List<DeviceInfo>> getDevices({required ButtplugClient client}) async {
    return await _instance.getDevices(client: client);
  }
  
  // 異步方法自動支持!
  static Future<void> startScanning({required ButtplugClient client}) async {
    return await _instance.startScanning(client: client);
  }
}

2. 設置複雜度差異

直接 FFI 設置

1. 編寫 Rust 代碼 (手動 C ABI)          ⏱️  2-3 天
2. 編寫 Dart FFI 綁定 (手動)           ⏱️  1-2 天  
3. 處理記憶體管理 (手動)               ⏱️  1 天
4. 除錯和測試                        ⏱️  2-3 天
5. 維護和更新 (每次都要手動同步)        ⏱️  持續成本高
---
總計: 約 1-2 週 + 高維護成本

flutter_rust_bridge 設置

1. 安裝工具                          ⏱️  10 分鐘
2. 編寫 Rust 代碼 (原生語法)          ⏱️  半天
3. 執行代碼生成                      ⏱️  2 分鐘
4. 測試和除錯                       ⏱️  半天
5. 維護 (重新執行生成即可)            ⏱️  2 分鐘
---
總計: 約 1 天 + 極低維護成本

3. 性能差異分析

直接 FFI (零抽象開銷)

Dart → FFI → Rust 函數
調用延遲: ~1-2μs
記憶體拷貝: 最少 (手動優化)

flutter_rust_bridge (微小開銷)

Dart → 生成的包裝 → FFI → Rust 函數
調用延遲: ~2-3μs (包裝層開銷)
記憶體拷貝: 輕微增加 (自動化轉換)

性能測試結果

#![allow(unused)]
fn main() {
// 測試:調用 10000 次簡單函數
直接 FFI:     平均 1.2μs per call
flutter_rust_bridge: 平均 1.8μs per call

// 差異:約 50% 開銷,但絕對值很小
}

4. 類型支持差異

直接 FFI 支持的類型

#![allow(unused)]
fn main() {
✅ 基本類型 (i32, f64, bool)
✅ 指針 (*const, *mut)  
⚠️ 字串 (需手動轉換 CString/CStr)
❌ 結構體 (需手動 repr(C))
❌ 枚舉 (需手動轉換為 u32)
❌ Vec (需手動分配/釋放)
❌ HashMap (不支持)
❌ Option (需手動 null 檢查)
❌ Result (需手動錯誤處理)
❌ async/Future (不支持)
}

flutter_rust_bridge 支持的類型

#![allow(unused)]
fn main() {
✅ 基本類型 (i32, f64, bool)
✅ 字串 (String, &str)
✅ 結構體 (自動轉換)
✅ 枚舉 (自動轉換)
✅ Vec<T> (自動轉換為 List)
✅ HashMap<K,V> (自動轉換為 Map)
✅ Option<T> (自動轉換為 nullable)
✅ Result<T,E> (自動轉換為異常)
✅ Future<T> (async 支持!)
✅ 自定義類型 (自動生成)
}

5. 錯誤處理差異

直接 FFI 錯誤處理

#![allow(unused)]
fn main() {
// Rust: 手動編碼錯誤
#[no_mangle]
pub extern "C" fn risky_operation() -> i32 {
    // 成功返回 0,錯誤返回負數
    // 調用方需要解釋錯誤碼
    -1 // 錯誤
}
}
// Dart: 手動檢查錯誤碼
void callRiskyOperation() {
  final result = _riskyOperation();
  if (result < 0) {
    throw Exception('Operation failed with code: $result');
  }
}

flutter_rust_bridge 錯誤處理

#![allow(unused)]
fn main() {
// Rust: 直接使用 Result
pub fn risky_operation() -> Result<String, String> {
    Err("Something went wrong".to_string())
}
}
// Dart: 自動轉換為異常
try {
  final result = await riskyOperation();
} on BridgeError catch (e) {
  print('Caught error: ${e.message}');
}

6. 記憶體管理差異

直接 FFI 記憶體管理

#![allow(unused)]
fn main() {
// Rust: 手動管理所有記憶體
#[no_mangle]
pub extern "C" fn get_string() -> *mut c_char {
    let s = CString::new("Hello").unwrap();
    s.into_raw() // 轉移所有權給 C
}

#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
    unsafe { CString::from_raw(s) }; // 手動釋放
}
}
// Dart: 手動調用釋放函數
String getString() {
  final ptr = _getString();
  try {
    return ptr.toDartString();
  } finally {
    _freeString(ptr); // 必須記得釋放!
  }
}

flutter_rust_bridge 記憶體管理

#![allow(unused)]
fn main() {
// Rust: 正常的 Rust 代碼,自動管理
pub fn get_string() -> String {
    "Hello".to_string() // 自動管理記憶體
}
}
// Dart: 無需手動釋放
String result = await getString(); // 自動處理記憶體

7. 開發體驗差異

直接 FFI 開發流程

1. 修改 Rust 代碼
2. 手動更新 C 綁定函數
3. 重新編譯 Rust
4. 手動更新 Dart FFI 綁定
5. 手動更新類型定義
6. 手動測試記憶體洩漏
7. 手動測試錯誤情況

flutter_rust_bridge 開發流程

1. 修改 Rust 代碼 (正常語法)
2. 執行: flutter_rust_bridge_codegen
3. 測試 (Dart 代碼自動更新)

8. 實際專案大小對比

直接 FFI 專案結構

project/
├── rust/
│   ├── src/
│   │   ├── lib.rs           (300+ 行 C ABI 代碼)
│   │   ├── ffi_utils.rs     (100+ 行工具函數)
│   │   └── memory.rs        (100+ 行記憶體管理)
│   └── Cargo.toml
├── lib/
│   ├── ffi_bindings.dart    (500+ 行手動綁定)
│   ├── types.dart           (200+ 行手動類型)
│   └── memory_manager.dart  (150+ 行記憶體管理)
└── pubspec.yaml

總代碼量: ~1250 行 (大部分是樣板代碼)
維護負擔: 高 (每次修改需要同步多個文件)

flutter_rust_bridge 專案結構

project/
├── rust/
│   ├── src/
│   │   └── lib.rs           (100 行原生 Rust)
│   └── Cargo.toml
├── lib/
│   ├── bridge_generated.dart (自動生成)
│   └── main.dart            (50 行業務邏輯)
├── build.yaml              (配置文件)
└── pubspec.yaml

總代碼量: ~150 行 (只有業務邏輯)
維護負擔: 低 (修改 Rust 後重新生成即可)

9. 適用場景建議

選擇直接 FFI 的場景

  • ✅ 對性能有極致要求 (如遊戲引擎、音視頻處理)
  • ✅ 需要與現有 C/C++ 代碼整合
  • ✅ 想要完全控制記憶體分配
  • ✅ 團隊有深厚的底層程式設計經驗
  • ❌ 快速原型開發
  • ❌ 頻繁修改介面

選擇 flutter_rust_bridge 的場景

  • ✅ 快速開發和原型製作
  • ✅ 複雜的資料結構交換
  • ✅ 需要異步支持
  • ✅ 團隊更熟悉高階語言
  • ✅ 頻繁修改和迭代
  • ❌ 對性能有極致要求
  • ❌ 需要與 C/C++ 整合

10. Buttplug 專案建議

考慮因素分析

因素直接 FFIflutter_rust_bridge
開發速度慢 ❌快 ✅
性能要求滿足 ✅滿足 ✅
類型複雜度高 (設備信息、事件) ❌輕鬆處理 ✅
異步需求高 (掃描、連接) ❌原生支持 ✅
維護成本高 ❌低 ✅
團隊經驗需要專家 ❌普通開發者 ✅

🎯 最終建議:flutter_rust_bridge

理由:

  1. 開發效率:Buttplug 有複雜的設備管理和事件處理,手動 FFI 工作量巨大
  2. 異步支持:設備掃描、連接都是異步操作,flutter_rust_bridge 原生支持
  3. 類型安全:設備信息、錯誤處理等複雜類型,自動轉換減少 bug
  4. 維護性:Buttplug 協議可能會更新,自動生成降低維護成本
  5. 性能足夠:對於設備控制,微秒級延遲差異不會影響用戶體驗

結論: 除非您的團隊有豐富的底層程式設計經驗且追求極致性能,否則 flutter_rust_bridge 是更明智的選擇。