FRB Patterns
Flutter Rust Bridge (FRB) generates Dart bindings for Rust types. Choosing the right annotation determines whether Dart or Rust controls construction and mutation.
Annotation Decision Framework
Section titled “Annotation Decision Framework”| Annotation | Control | Mutation | Purpose | Example |
|---|---|---|---|---|
#[frb(non_opaque)] | Dart constructs | Immutable / Dart mutates fields | DTO, value type, snapshot | PropertySchema, WhiteboardElement |
#[frb(opaque)] | Rust controls | Rust methods via &mut self | Container with CRUD, handle | PropertySchemaList, NotePropertyValues |
Non-opaque (Dart constructs)
Section titled “Non-opaque (Dart constructs)”Use for DTOs, value types, snapshots, and enum variants. Dart can construct these directly and access all public fields.
#[frb(non_opaque)]#[derive(Clone, Serialize, Deserialize)]pub struct PropertySchema { pub key: String, pub name: String, pub property_type: PropertyType,}
#[frb(non_opaque)]pub enum WhiteboardElement { Path(PathElement), Shape(ShapeElement), Text(TextElement), // Generates a Dart sealed class with variants}Opaque (Rust controls)
Section titled “Opaque (Rust controls)”Use for containers with CRUD operations, session handles, or any type where Rust must manage the internal state.
#[frb(opaque)]pub struct PropertySchemaList { pub schemas: Vec<PropertySchema>,}
impl PropertySchemaList { pub fn new() -> Self { /* ... */ } // FRB: factory pub fn add(&mut self, s: PropertySchema) { /* ... */ } // FRB: mutation pub fn len(&self) -> usize { /* ... */ } // FRB: query pub fn all_schemas(&self) -> Vec<PropertySchema> { /* ... */ } // FRB: snapshot}
#[frb(ignore)]impl PropertySchemaList { pub fn get(&self, key: &str) -> Option<&PropertySchema> { /* ... */ } // Returns a reference -- cannot cross FFI}Key Rules
Section titled “Key Rules”1. #[frb(ignore)] goes on entire impl blocks
Section titled “1. #[frb(ignore)] goes on entire impl blocks”FRB does not support ignoring individual methods. Split your impl into two blocks: one for FRB-exposed methods, one for ignored methods (typically those returning references).
// Exposed to FRBimpl MyType { pub fn count(&self) -> usize { self.items.len() } pub fn snapshot(&self) -> Vec<Item> { self.items.clone() }}
// Hidden from FRB#[frb(ignore)]impl MyType { pub fn get(&self, idx: usize) -> Option<&Item> { self.items.get(idx) } pub fn iter(&self) -> impl Iterator<Item = &Item> { self.items.iter() }}2. Only crates/api uses #[frb(...)] attributes
Section titled “2. Only crates/api uses #[frb(...)] attributes”All other crates use doc comment syntax instead. This keeps the flutter_rust_bridge dependency confined to the API crate.
// In crates/api -- attribute syntax#[frb(non_opaque)]pub struct ApiDto { /* ... */ }
// In crates/core, crates/session, etc. -- doc comment syntax/// flutter_rust_bridge:non_opaquepub struct CoreDto { /* ... */ }
/// flutter_rust_bridge:syncpub fn get_version() -> String { /* ... */ }
/// flutter_rust_bridge:ignoreimpl InternalType { /* ... */ }3. Never use /// flutter_rust_bridge:frb
Section titled “3. Never use /// flutter_rust_bridge:frb”This produces invalid #[frb(frb)] during codegen and crashes the code generator. Always use the specific annotation:
// WRONG -- crashes codegen/// flutter_rust_bridge:frb
// CORRECT/// flutter_rust_bridge:non_opaque/// flutter_rust_bridge:sync/// flutter_rust_bridge:ignore4. Doc comments do not work on individual methods
Section titled “4. Doc comments do not work on individual methods”Just as #[frb(ignore)] must go on entire impl blocks, /// flutter_rust_bridge:ignore must also be placed on the impl block, not on individual functions within it.
5. third_party/ in Dart output is auto-generated
Section titled “5. third_party/ in Dart output is auto-generated”When FRB scans external crates transitively, it places their generated Dart bindings in lib/src/rust/third_party/. This directory is fully managed by codegen — do not edit it manually.
Types That Must Live in Scanned Crates
Section titled “Types That Must Live in Scanned Crates”FRB only generates non_opaque Dart classes for types it can see during codegen. If a type is defined in a crate that FRB does not scan, it will be generated as opaque regardless of annotations.
The scanned entry point is crates/api. Types in crates/core are scanned transitively (since api depends on it). Types in crates/session or platform crates need to be re-exported through core or api if they must be non-opaque on the Dart side.
Related
Section titled “Related”FRB Codegen— how to regenerate bindings after API changesFRB API Layer— the thin open/dispatch/close surface