Skip to content

Flutter Rust Bridge

flutter_rust_bridge(FRB)是连接 Flutter/Dart 与 Rust 的代码生成工具。它自动生成 FFI 绑定代码,让你可以在 Flutter 中直接调用 Rust 函数,享受 Rust 的高性能和内存安全。当前主流版本为 v2

适用场景:

  • CPU 密集型计算(图像处理、加解密、音视频编解码)
  • 复用已有 Rust 库
  • 需要跨平台(Android / iOS / Desktop / Web)的原生性能
  • 替代平台通道(Platform Channel)的繁琐 FFI 代码

环境准备

安装 Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update stable

# Android 编译目标
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android

# iOS 编译目标
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim

# macOS / Linux / Windows Desktop
rustup target add x86_64-apple-darwin aarch64-apple-darwin    # macOS

安装 FRB CLI

cargo install flutter_rust_bridge_codegen

# Android 交叉编译工具
cargo install cargo-ndk

# iOS 多架构打包
cargo install cargo-lipo

安装 Flutter 侧依赖

# pubspec.yaml
dependencies:
  flutter_rust_bridge: ^2.0.0

dev_dependencies:
  ffigen: ^9.0.0

创建项目

方式一:从模板创建(推荐)

flutter_rust_bridge_codegen create my_app
cd my_app

生成的项目结构:

my_app/
├── lib/
│   ├── main.dart
│   └── src/
│       └── rust/              # 自动生成的 Dart 绑定代码(勿手动修改)
│           ├── api/
│           │   └── simple.dart
│           ├── frb_generated.dart
│           └── frb_generated.io.dart
├── rust/                      # Rust 核心逻辑(你的代码写这里)
│   ├── Cargo.toml
│   └── src/
│       ├── lib.rs
│       └── api/
│           └── simple.rs
├── android/
├── ios/
└── pubspec.yaml

方式二:添加到已有 Flutter 项目

cd your_flutter_project
flutter_rust_bridge_codegen integrate

代码生成工作流

1. 编写 Rust 代码(rust/src/api/)
        ↓
2. 运行代码生成
   flutter_rust_bridge_codegen generate
        ↓
3. 自动生成 Dart 绑定(lib/src/rust/)
        ↓
4. 在 Flutter 中调用

开发时开启监听模式,保存 Rust 文件后自动重新生成:

flutter_rust_bridge_codegen generate --watch

基础类型映射

Rust 类型 Dart 类型 说明
i8 / i16 / i32 int
i64 int Dart int 是 64 位
u8 / u16 / u32 int
u64 BigInt 超出 Dart int 范围
f32 / f64 double
bool bool
String String
&str String
Vec<u8> Uint8List 字节数组
Vec<T> List<T>
[T; N] List<T> 固定长度数组
Option<T> T? 可空类型
HashMap<K, V> Map<K, V>
(A, B) (A, B) Dart Record
() void

基础函数

普通函数

// rust/src/api/simple.rs
use flutter_rust_bridge::frb;

/// 简单加法
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// 字符串处理
pub fn greet(name: String) -> String {
    format!("你好,{}!", name)
}

/// 处理可选值
pub fn double_if_positive(value: i32) -> Option<i32> {
    if value > 0 { Some(value * 2) } else { None }
}

/// 处理字节数组
pub fn reverse_bytes(data: Vec<u8>) -> Vec<u8> {
    data.into_iter().rev().collect()
}
// Dart 调用
import 'package:my_app/src/rust/api/simple.dart';
import 'package:my_app/src/rust/frb_generated.dart';

void main() async {
  await RustLib.init();  // 初始化,只需调用一次
  runApp(const MyApp());
}

// 在组件中调用
final result = await add(a: 1, b: 2);           // 3
final greeting = await greet(name: "张三");      // "你好,张三!"
final doubled = await doubleIfPositive(value: 5); // 10
final reversed = await reverseBytes(data: Uint8List.fromList([1, 2, 3]));

异步函数

FRB v2 中,所有 Rust 函数在 Dart 侧都是异步的(返回 Future)。若 Rust 函数本身也是异步的,使用 async

// rust/src/api/network.rs
use flutter_rust_bridge::frb;

/// 异步网络请求(使用 tokio)
pub async fn fetch_data(url: String) -> Result<String, String> {
    let body = reqwest::get(&url)
        .await
        .map_err(|e| e.to_string())?
        .text()
        .await
        .map_err(|e| e.to_string())?;
    Ok(body)
}

/// 异步文件读取
pub async fn read_file(path: String) -> Result<Vec<u8>, String> {
    tokio::fs::read(&path)
        .await
        .map_err(|e| e.to_string())
}
// Cargo.toml
[dependencies]
flutter_rust_bridge = "2"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
// Dart 调用
try {
  final html = await fetchData(url: 'https://example.com');
  print(html);
} catch (e) {
  print('请求失败:$e');
}

结构体(Struct)

基础结构体

// rust/src/api/models.rs
use flutter_rust_bridge::frb;

/// FRB 会为此结构体生成对应的 Dart 类
pub struct User {
    pub id: u32,
    pub name: String,
    pub email: Option<String>,
    pub age: u8,
    pub active: bool,
}

pub fn create_user(name: String, age: u8) -> User {
    User {
        id: rand::random(),
        name,
        email: None,
        age,
        active: true,
    }
}

pub fn get_users() -> Vec<User> {
    vec![
        User { id: 1, name: "张三".into(), email: Some("zhang@example.com".into()), age: 25, active: true },
        User { id: 2, name: "李四".into(), email: None, age: 30, active: false },
    ]
}
// 自动生成的 Dart 类(无需手写)
// class User {
//   final int id;
//   final String name;
//   final String? email;
//   final int age;
//   final bool active;
// }

final user = await createUser(name: "张三", age: 25);
print(user.name);    // "张三"
print(user.email);   // null

final users = await getUsers();
for (final u in users) {
  print('${u.id}: ${u.name}');
}

结构体方法(impl)

使用 #[frb] 属性暴露方法:

pub struct Counter {
    pub count: i32,
    pub step: i32,
}

impl Counter {
    /// 构造函数(Dart 侧使用 Counter.newWithStep())
    #[frb(sync)]
    pub fn new_with_step(step: i32) -> Counter {
        Counter { count: 0, step }
    }

    /// 实例方法
    pub fn increment(&mut self) {
        self.count += self.step;
    }

    pub fn decrement(&mut self) {
        self.count -= self.step;
    }

    pub fn reset(&mut self) {
        self.count = 0;
    }

    #[frb(sync)]
    pub fn value(&self) -> i32 {
        self.count
    }
}
final counter = Counter.newWithStep(step: 5);
await counter.increment();
await counter.increment();
print(counter.value());  // 10
await counter.reset();
print(counter.value());  // 0

枚举(Enum)

简单枚举

pub enum Direction {
    North,
    South,
    East,
    West,
}

pub fn describe_direction(dir: Direction) -> String {
    match dir {
        Direction::North => "向北走".to_string(),
        Direction::South => "向南走".to_string(),
        Direction::East  => "向东走".to_string(),
        Direction::West  => "向西走".to_string(),
    }
}
final desc = await describeDirection(dir: Direction.north);
print(desc);  // "向北走"

带数据的枚举(ADT)

pub enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { base: f64, height: f64 },
}

pub fn area(shape: Shape) -> f64 {
    match shape {
        Shape::Circle { radius }            => std::f64::consts::PI * radius * radius,
        Shape::Rectangle { width, height }  => width * height,
        Shape::Triangle { base, height }    => 0.5 * base * height,
    }
}
final circleArea = await area(
  shape: Shape_Circle(radius: 5.0),
);
final rectArea = await area(
  shape: Shape_Rectangle(width: 4.0, height: 6.0),
);
print(circleArea);  // 78.53981633974483

错误处理

anyhow::Result(推荐)

// Cargo.toml
// anyhow = "1"

use anyhow::Result;

pub fn parse_number(s: String) -> Result<i32> {
    let n = s.trim().parse::<i32>()?;
    if n < 0 {
        anyhow::bail!("数字不能为负数:{}", n);
    }
    Ok(n)
}

pub fn read_config(path: String) -> Result<String> {
    let content = std::fs::read_to_string(&path)
        .map_err(|e| anyhow::anyhow!("读取文件 {} 失败:{}", path, e))?;
    Ok(content)
}
// Rust 的 anyhow::Error 在 Dart 侧抛出为异常
try {
  final n = await parseNumber(s: "42");
  print(n);  // 42
} on AnyhowException catch (e) {
  print('解析失败:${e.message}');
}

// 或用 try-catch
try {
  final config = await readConfig(path: '/etc/app.conf');
} catch (e) {
  print(e);
}

自定义错误枚举

use flutter_rust_bridge::frb;

#[derive(Debug)]
pub enum AppError {
    NotFound { resource: String },
    PermissionDenied { action: String },
    NetworkError { message: String },
    ParseError { input: String, reason: String },
}

impl std::fmt::Display for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AppError::NotFound { resource }         => write!(f, "未找到:{}", resource),
            AppError::PermissionDenied { action }   => write!(f, "无权限:{}", action),
            AppError::NetworkError { message }      => write!(f, "网络错误:{}", message),
            AppError::ParseError { input, reason }  => write!(f, "解析 '{}' 失败:{}", input, reason),
        }
    }
}

pub fn find_user(id: u32) -> Result<User, AppError> {
    if id == 0 {
        return Err(AppError::NotFound { resource: format!("用户 {}", id) });
    }
    Ok(User { id, name: "张三".into(), email: None, age: 25, active: true })
}
try {
  final user = await findUser(id: 0);
} on AppError_NotFound catch (e) {
  print('未找到:${e.resource}');
} on AppError_PermissionDenied catch (e) {
  print('无权限:${e.action}');
} on AppError catch (e) {
  print('其他错误:$e');
}

流(Stream)

StreamSink 让 Rust 向 Dart 持续推送数据,类似 Dart 的 Stream

use flutter_rust_bridge::frb;

/// 进度推送
pub async fn download_file(
    url: String,
    sink: StreamSink<f64>,  // 推送下载进度 0.0 ~ 1.0
) -> Result<Vec<u8>, String> {
    let response = reqwest::get(&url).await.map_err(|e| e.to_string())?;
    let total = response.content_length().unwrap_or(0);
    let mut downloaded = 0u64;
    let mut bytes = Vec::new();

    let mut stream = response.bytes_stream();
    while let Some(chunk) = stream.next().await {
        let chunk = chunk.map_err(|e| e.to_string())?;
        downloaded += chunk.len() as u64;
        bytes.extend_from_slice(&chunk);

        if total > 0 {
            let progress = downloaded as f64 / total as f64;
            sink.add(progress);  // 推送进度
        }
    }
    Ok(bytes)
}

/// 持续推送传感器数据
pub fn subscribe_sensor(sink: StreamSink<SensorData>) {
    std::thread::spawn(move || {
        loop {
            let data = read_sensor();
            if sink.add(data).is_err() {
                break;  // Dart 侧关闭 Stream 时退出
            }
            std::thread::sleep(std::time::Duration::from_millis(100));
        }
    });
}

pub struct SensorData {
    pub timestamp: i64,
    pub temperature: f32,
    pub humidity: f32,
}
// 监听下载进度
final stream = downloadFile(url: 'https://example.com/file.zip');

stream.listen(
  (progress) => print('下载进度:${(progress * 100).toStringAsFixed(1)}%'),
  onError: (e) => print('下载失败:$e'),
  onDone: () => print('下载完成'),
);

// 传感器数据流
late StreamSubscription subscription;

@override
void initState() {
  super.initState();
  subscription = subscribeSensor().listen((data) {
    setState(() {
      temperature = data.temperature;
      humidity = data.humidity;
    });
  });
}

@override
void dispose() {
  subscription.cancel();  // 取消订阅,Rust 侧感知到后退出循环
  super.dispose();
}

不透明类型(Opaque Types)

当 Rust 类型不需要(或无法)直接映射到 Dart 时,使用 #[frb(opaque)] 将其作为不透明句柄传递:

use flutter_rust_bridge::frb;

/// 数据库连接(Dart 只持有句柄,不关心内部结构)
#[frb(opaque)]
pub struct Database {
    conn: sqlite::Connection,
    path: String,
}

impl Database {
    pub fn open(path: String) -> Result<Database, String> {
        let conn = sqlite::open(&path).map_err(|e| e.to_string())?;
        Ok(Database { conn, path })
    }

    pub fn execute(&self, sql: String) -> Result<(), String> {
        self.conn.execute(&sql).map_err(|e| e.to_string())
    }

    pub fn query_users(&self) -> Result<Vec<User>, String> {
        // 查询逻辑
        todo!()
    }

    pub fn close(self) {
        // 消耗 self,自动关闭连接
    }
}
// Dart 侧 Database 是一个不透明对象,无法访问内部字段
final db = await Database.open(path: '/data/app.db');

await db.execute(sql: 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY)');
final users = await db.queryUsers();

// 不再需要时释放
db.dispose();  // FRB 自动管理内存

#[frb] 常用属性

use flutter_rust_bridge::frb;

/// 同步函数(无需 await,直接返回值)
/// 注意:只适合非阻塞的轻量操作
#[frb(sync)]
pub fn add_sync(a: i32, b: i32) -> i32 {
    a + b
}

/// 忽略此函数,不生成 Dart 绑定
#[frb(ignore)]
pub fn internal_helper() {}

/// 初始化函数(应用启动时自动调用)
#[frb(init)]
pub fn init_app() {
    // 初始化日志、全局状态等
    env_logger::init();
}

/// 指定 Dart 侧的函数名
#[frb(dart_code = "
  static Future<int> fromString(String s) => parseIntFromString(s: s);
")]
pub fn parse_int_from_string(s: String) -> Result<i32, String> {
    s.trim().parse().map_err(|e: std::num::ParseIntError| e.to_string())
}
// 同步调用(不需要 await)
final sum = addSync(a: 1, b: 2);  // 3

// 非同步调用
final sum2 = await add(a: 1, b: 2);  // 3

在 Rust 中调用 Dart 回调

use flutter_rust_bridge::DartFnFuture;

/// 接收 Dart 函数作为回调
pub async fn process_with_callback(
    data: Vec<u8>,
    on_progress: impl Fn(f64) -> DartFnFuture<()>,
) -> Vec<u8> {
    let total = data.len();
    let mut result = Vec::new();

    for (i, chunk) in data.chunks(1024).enumerate() {
        // 处理数据块
        result.extend_from_slice(chunk);

        let progress = (i + 1) as f64 / (total / 1024 + 1) as f64;
        on_progress(progress).await;  // 调用 Dart 回调
    }
    result
}
final result = await processWithCallback(
  data: largeData,
  onProgress: (progress) async {
    setState(() => _progress = progress);
  },
);

多线程与并发

use std::sync::{Arc, Mutex};
use flutter_rust_bridge::frb;

/// 线程安全的共享状态
#[frb(opaque)]
pub struct SharedState {
    inner: Arc<Mutex<StateInner>>,
}

struct StateInner {
    data: Vec<String>,
    count: u32,
}

impl SharedState {
    pub fn new() -> SharedState {
        SharedState {
            inner: Arc::new(Mutex::new(StateInner { data: vec![], count: 0 })),
        }
    }

    pub fn add(&self, item: String) {
        let mut inner = self.inner.lock().unwrap();
        inner.data.push(item);
        inner.count += 1;
    }

    pub fn get_count(&self) -> u32 {
        self.inner.lock().unwrap().count
    }

    pub fn get_all(&self) -> Vec<String> {
        self.inner.lock().unwrap().data.clone()
    }
}

/// CPU 密集型并行计算
pub async fn parallel_process(items: Vec<String>) -> Vec<String> {
    use rayon::prelude::*;

    // rayon 自动并行,不阻塞 Dart 线程
    tokio::task::spawn_blocking(move || {
        items.par_iter()
            .map(|s| s.to_uppercase())
            .collect()
    }).await.unwrap()
}

平台配置

Android

// android/app/build.gradle
android {
    defaultConfig {
        // 最低 API 21
        minSdkVersion 21
    }
}
# 构建 Android .so 库
cargo ndk -t armeabi-v7a -t arm64-v8a -t x86_64 -o ../android/app/src/main/jniLibs build --release

FRB 的 Gradle 插件会自动处理,通常无需手动运行上述命令,直接 flutter run 即可。

iOS

# 构建 iOS 静态库
cargo lipo --release --targets aarch64-apple-ios x86_64-apple-ios

# 或使用 FRB 提供的脚本
flutter_rust_bridge_codegen build-web   # 仅 Web
# ios/Podfile
platform :ios, '12.0'

macOS Desktop

# 在 macOS 上直接运行
flutter run -d macos

Web(实验性)

# 安装 wasm-pack
cargo install wasm-pack

# FRB v2 支持 Web,但需要额外配置
flutter_rust_bridge_codegen generate --web

完整示例:图片处理器

// rust/src/api/image_processor.rs
use flutter_rust_bridge::frb;
use image::{DynamicImage, ImageFormat};
use std::io::Cursor;

pub struct ImageInfo {
    pub width: u32,
    pub height: u32,
    pub format: String,
    pub size_bytes: usize,
}

/// 获取图片信息
pub fn get_image_info(bytes: Vec<u8>) -> Result<ImageInfo, String> {
    let img = image::load_from_memory(&bytes).map_err(|e| e.to_string())?;
    Ok(ImageInfo {
        width: img.width(),
        height: img.height(),
        format: "JPEG".to_string(),
        size_bytes: bytes.len(),
    })
}

/// 压缩图片
pub async fn compress_image(
    bytes: Vec<u8>,
    quality: u8,
    max_width: Option<u32>,
    sink: StreamSink<f32>,   // 推送进度
) -> Result<Vec<u8>, String> {
    tokio::task::spawn_blocking(move || {
        sink.add(0.1);
        let img = image::load_from_memory(&bytes).map_err(|e| e.to_string())?;
        sink.add(0.4);

        let img = if let Some(max_w) = max_width {
            if img.width() > max_w {
                img.resize(max_w, u32::MAX, image::imageops::FilterType::Lanczos3)
            } else {
                img
            }
        } else {
            img
        };

        sink.add(0.7);

        let mut output = Cursor::new(Vec::new());
        img.write_to(&mut output, ImageFormat::Jpeg).map_err(|e| e.to_string())?;
        sink.add(1.0);

        Ok(output.into_inner())
    }).await.map_err(|e| e.to_string())?
}

/// 生成缩略图
pub fn generate_thumbnail(bytes: Vec<u8>, size: u32) -> Result<Vec<u8>, String> {
    let img = image::load_from_memory(&bytes).map_err(|e| e.to_string())?;
    let thumb = img.thumbnail(size, size);

    let mut output = Cursor::new(Vec::new());
    thumb.write_to(&mut output, ImageFormat::Jpeg).map_err(|e| e.to_string())?;
    Ok(output.into_inner())
}
// lib/pages/image_page.dart
class ImageProcessorPage extends StatefulWidget { ... }

class _ImageProcessorPageState extends State<ImageProcessorPage> {
  double _progress = 0;
  Uint8List? _compressed;

  Future<void> _pickAndCompress() async {
    final picker = ImagePicker();
    final file = await picker.pickImage(source: ImageSource.gallery);
    if (file == null) return;

    final bytes = await file.readAsBytes();

    // 获取图片信息
    final info = await getImageInfo(bytes: bytes);
    print('${info.width}x${info.height}, ${info.sizeBytes} bytes');

    // 压缩(带进度)
    final stream = compressImage(
      bytes: bytes,
      quality: 80,
      maxWidth: 1080,
    );

    Uint8List? result;
    await for (final event in stream) {
      // StreamSink 的最后一个事件是 Result
      if (event is RustStreamEvent_Progress) {
        setState(() => _progress = event.field0);
      }
    }

    // 生成缩略图
    final thumb = await generateThumbnail(bytes: bytes, size: 200);
    setState(() => _compressed = Uint8List.fromList(thumb));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          LinearProgressIndicator(value: _progress),
          if (_compressed != null) Image.memory(_compressed!),
          ElevatedButton(onPressed: _pickAndCompress, child: const Text('选择并压缩')),
        ],
      ),
    );
  }
}

调试与常见问题

日志输出

// Cargo.toml
// log = "0.4"
// env_logger = "0.10"  (仅 Android/Desktop)
// oslog = "0.2"        (仅 iOS)

#[frb(init)]
pub fn init_app() {
    #[cfg(target_os = "android")]
    android_logger::init_once(
        android_logger::Config::default().with_min_level(log::Level::Debug),
    );

    #[cfg(target_os = "ios")]
    oslog::OsLogger::new("com.example.app").init().ok();

    log::info!("Rust 初始化完成");
}

常见报错

# 1. 找不到动态库
# 原因:未编译 Rust 或 .so/.dylib 文件路径不对
# 解决:flutter clean && flutter run

# 2. 代码生成失败
# 原因:Rust 代码有编译错误
# 解决:先 cargo build 确保 Rust 代码本身能编译

# 3. 类型不支持
# 原因:使用了 FRB 不支持的 Rust 类型
# 解决:用 #[frb(opaque)] 包装,或转换为支持的类型

# 4. 函数签名冲突
# 原因:两个模块有同名函数
# 解决:在 Dart 侧会自动加模块前缀,检查生成的代码

性能调优

// 避免不必要的数据拷贝:大块数据用引用
// 但注意 FRB 的生命周期限制,通常只能传 owned 值

// CPU 密集型任务放在 spawn_blocking 中
pub async fn heavy_computation(data: Vec<f64>) -> Vec<f64> {
    tokio::task::spawn_blocking(move || {
        data.iter().map(|x| x.sqrt()).collect()
    }).await.unwrap()
}

// 批量处理优于单次处理(减少 FFI 跨越次数)
// ✗ 在循环中逐个调用 Rust 函数
for item in items {
    process_item(item: item);
}
// ✓ 一次传入全部数据
process_items(items: items);

与 Platform Channel 对比

flutter_rust_bridge Platform Channel
语言 Rust Kotlin / Swift / Java / ObjC
类型安全 自动生成,完全类型安全 手动维护,容易出错
性能 直接 FFI,几乎无开销 序列化开销较大
代码量 极少样板代码 需要大量胶水代码
调试 Rust 工具链(lldb/gdb) 原生 IDE
跨平台 一份 Rust 代码全平台 每个平台单独实现
生态 crates.io(丰富) 各平台原生生态
适用场景 计算密集、跨平台逻辑 访问平台专有 API