Rust implementation of FlatBuffers providing memory-safe access with zero-cost abstractions and integration with Rust's ownership model and type system. The Rust binding leverages Rust's compile-time guarantees while maintaining the performance benefits of zero-copy deserialization.
Add FlatBuffers to your Rust project using Cargo.
# Cargo.toml
[dependencies]
flatbuffers = "25.2.10"
# Optional features
[dependencies.flatbuffers]
version = "25.2.10"
features = ["std"] # Default feature for std library support
# For no_std environments
[dependencies.flatbuffers]
version = "25.2.10"
default-features = false
# With serde support for serialization interop
[dependencies.flatbuffers]
version = "25.2.10"
features = ["serialize"]// Core imports
use flatbuffers::{FlatBufferBuilder, Vector, WIPOffset, Table, Verifiable};
// Additional common imports
use std::collections::HashMap;
use std::io::{Read, Write};Main builder struct for constructing FlatBuffer data in Rust applications.
pub struct FlatBufferBuilder<'a> {
// Internal fields (private)
}
impl<'a> FlatBufferBuilder<'a> {
/**
* Create new FlatBufferBuilder with default capacity
* @return New builder instance
*/
pub fn new() -> Self;
/**
* Create FlatBufferBuilder with specific initial capacity
* @param initial_size Initial buffer size in bytes
* @return New builder instance
*/
pub fn with_capacity(initial_size: usize) -> Self;
/**
* Reset builder for reuse, keeping allocated memory
*/
pub fn reset(&mut self);
/**
* Get current buffer size
* @return Size in bytes
*/
pub fn len(&self) -> usize;
/**
* Check if buffer is empty
* @return True if empty
*/
pub fn is_empty(&self) -> bool;
/**
* Create string and return its offset
* @param s String slice to store
* @return WIPOffset to string
*/
pub fn create_string(&mut self, s: &str) -> WIPOffset<&str>;
/**
* Create vector from slice
* @param items Slice of items to store
* @return WIPOffset to vector
*/
pub fn create_vector<T: Copy + 'static>(&mut self, items: &[T]) -> WIPOffset<Vector<'_, T>>;
/**
* Create vector of strings
* @param items Slice of string offsets
* @return WIPOffset to vector
*/
pub fn create_vector_of_strings(&mut self, items: &[WIPOffset<&str>]) -> WIPOffset<Vector<'_, WIPOffset<&str>>>;
/**
* Create vector of tables
* @param items Slice of table offsets
* @return WIPOffset to vector
*/
pub fn create_vector_of_tables<T>(&mut self, items: &[WIPOffset<T>]) -> WIPOffset<Vector<'_, WIPOffset<T>>>;
/**
* Start building a table
* @param num_fields Number of fields in table
*/
pub fn start_table(&mut self, num_fields: usize);
/**
* Add field to current table
* @param slot Field slot number
* @param value Value to add
* @param default_value Default value for comparison
*/
pub fn push_slot<T: Copy + PartialEq>(&mut self, slot: usize, value: T, default_value: T);
/**
* Add offset field to current table
* @param slot Field slot number
* @param offset Offset value
*/
pub fn push_slot_always<T>(&mut self, slot: usize, offset: WIPOffset<T>);
/**
* End table construction
* @return WIPOffset to completed table
*/
pub fn end_table(&mut self) -> WIPOffset<Table>;
/**
* Finish buffer with root table
* @param root Root table offset
* @param file_identifier Optional 4-byte file identifier
*/
pub fn finish<T>(&mut self, root: WIPOffset<T>, file_identifier: Option<&str>);
/**
* Finish buffer with size prefix
* @param root Root table offset
* @param file_identifier Optional 4-byte file identifier
*/
pub fn finish_size_prefixed<T>(&mut self, root: WIPOffset<T>, file_identifier: Option<&str>);
/**
* Get finished buffer data
* @return Slice containing FlatBuffer data
*/
pub fn finished_data(&self) -> &[u8];
/**
* Get finished buffer data as mutable slice
* @return Mutable slice containing FlatBuffer data
*/
pub fn finished_data_mut(&mut self) -> &mut [u8];
/**
* Take ownership of finished buffer
* @return Vector containing FlatBuffer data
*/
pub fn collapse(self) -> Vec<u8>;
}
impl<'a> Default for FlatBufferBuilder<'a> {
fn default() -> Self {
Self::new()
}
}Usage Example:
use flatbuffers::FlatBufferBuilder;
// Create builder
let mut builder = FlatBufferBuilder::new();
// Create string
let name = builder.create_string("Player");
// Create vector
let scores = vec![100, 200, 300, 400, 500];
let scores_offset = builder.create_vector(&scores);
// Create table
builder.start_table(3);
builder.push_slot_always(0, name); // name field (slot 0)
builder.push_slot(1, 42i32, 0); // level field (slot 1)
builder.push_slot_always(2, scores_offset); // scores field (slot 2)
let player = builder.end_table();
// Finish buffer
builder.finish(player, None);
// Get binary data
let buffer = builder.finished_data();
println!("Buffer size: {} bytes", buffer.len());Work-in-progress offset type providing type safety during buffer construction.
/**
* Work-in-progress offset type for type-safe buffer construction
* @param T Type being offset to
*/
pub struct WIPOffset<T> {
// Internal offset value (private)
}
impl<T> WIPOffset<T> {
/**
* Get raw offset value
* @return Offset as u32
*/
pub fn value(&self) -> u32;
}
impl<T> Copy for WIPOffset<T> {}
impl<T> Clone for WIPOffset<T> {
fn clone(&self) -> Self { *self }
}
// Conversion traits
impl<T> From<WIPOffset<T>> for u32 {
fn from(offset: WIPOffset<T>) -> u32 {
offset.value()
}
}Table access functionality for reading FlatBuffer data with Rust's memory safety guarantees.
/**
* Table type for accessing FlatBuffer table data
*/
#[derive(Debug, Clone, Copy)]
pub struct Table<'a> {
pub buf: &'a [u8], // Buffer containing table data
pub loc: usize, // Location of table in buffer
}
impl<'a> Table<'a> {
/**
* Create new table accessor
* @param buf Buffer containing data
* @param loc Table location in buffer
* @return Table instance
*/
pub fn new(buf: &'a [u8], loc: usize) -> Self;
/**
* Get field offset from vtable
* @param vtable_offset Offset in vtable
* @return Field offset or None if not present
*/
pub fn get<T: Copy>(&self, vtable_offset: usize) -> Option<T>;
/**
* Get field with default value
* @param vtable_offset Offset in vtable
* @param default_value Default value if field missing
* @return Field value or default
*/
pub fn get_with_default<T: Copy>(&self, vtable_offset: usize, default_value: T) -> T;
/**
* Get string at field offset
* @param vtable_offset Offset in vtable
* @return Optional string slice
*/
pub fn get_string(&self, vtable_offset: usize) -> Option<&'a str>;
/**
* Get vector at field offset
* @param vtable_offset Offset in vtable
* @return Optional vector
*/
pub fn get_vector<T: Copy>(&self, vtable_offset: usize) -> Option<Vector<'a, T>>;
/**
* Get table at field offset
* @param vtable_offset Offset in vtable
* @return Optional table
*/
pub fn get_table(&self, vtable_offset: usize) -> Option<Table<'a>>;
/**
* Get union table at field offset
* @param vtable_offset Offset in vtable
* @return Optional table for union access
*/
pub fn get_union_table(&self, vtable_offset: usize) -> Option<Table<'a>>;
}Vector type for accessing FlatBuffer vector data with iterator support.
/**
* Vector type for accessing FlatBuffer vector data
* @param T Element type
*/
#[derive(Debug, Clone, Copy)]
pub struct Vector<'a, T: Copy> {
buf: &'a [u8], // Buffer containing vector data
pos: usize, // Position of vector in buffer
len: usize, // Number of elements
_phantom: std::marker::PhantomData<T>,
}
impl<'a, T: Copy> Vector<'a, T> {
/**
* Get vector length
* @return Number of elements
*/
pub fn len(&self) -> usize;
/**
* Check if vector is empty
* @return True if empty
*/
pub fn is_empty(&self) -> bool;
/**
* Get element at index
* @param index Element index
* @return Element value
*/
pub fn get(&self, index: usize) -> T;
/**
* Safe element access with bounds checking
* @param index Element index
* @return Optional element value
*/
pub fn safe_get(&self, index: usize) -> Option<T>;
/**
* Get raw bytes of vector data
* @return Byte slice containing vector elements
*/
pub fn bytes(&self) -> &'a [u8];
/**
* Convert to slice (zero-copy when possible)
* @return Slice view of vector data
*/
pub fn as_slice(&self) -> &'a [T];
/**
* Create iterator over vector elements
* @return Iterator over elements
*/
pub fn iter(&self) -> VectorIter<'a, T>;
}
// Iterator implementation
impl<'a, T: Copy> IntoIterator for Vector<'a, T> {
type Item = T;
type IntoIter = VectorIter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
/**
* Iterator over vector elements
*/
pub struct VectorIter<'a, T: Copy> {
vector: Vector<'a, T>,
index: usize,
}
impl<'a, T: Copy> Iterator for VectorIter<'a, T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.vector.len() {
let item = self.vector.get(self.index);
self.index += 1;
Some(item)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.vector.len() - self.index;
(remaining, Some(remaining))
}
}
impl<'a, T: Copy> ExactSizeIterator for VectorIter<'a, T> {}When using flatc --rust, the compiler generates Rust code following these patterns.
// Example generated Rust (from monster.fbs):
use flatbuffers::{FlatBufferBuilder, Vector, WIPOffset, Table};
// Vec3 struct definition
#[derive(Debug, Clone, PartialEq)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Default for Vec3 {
fn default() -> Self {
Self { x: 0.0, y: 0.0, z: 0.0 }
}
}
impl Vec3 {
/**
* Pack Vec3 into FlatBufferBuilder
* @param builder FlatBuffer builder
* @param vec3 Vec3 instance to pack
* @return Offset to packed struct
*/
pub fn pack(builder: &mut FlatBufferBuilder, vec3: &Vec3) -> WIPOffset<Vec3> {
builder.prep(4, 12);
builder.write_f32(vec3.z);
builder.write_f32(vec3.y);
builder.write_f32(vec3.x);
WIPOffset::new(builder.offset())
}
/**
* Unpack Vec3 from buffer
* @param buf Buffer containing data
* @param pos Position in buffer
* @return Vec3 instance
*/
pub fn unpack(buf: &[u8], pos: usize) -> Self {
use flatbuffers::byte_slice_to_f32;
Self {
x: byte_slice_to_f32(&buf[pos..pos+4]),
y: byte_slice_to_f32(&buf[pos+4..pos+8]),
z: byte_slice_to_f32(&buf[pos+8..pos+12]),
}
}
}
// Monster table definition
#[derive(Debug, Clone, Copy)]
pub struct Monster<'a> {
pub _tab: Table<'a>,
}
impl<'a> Monster<'a> {
/**
* Get Monster from buffer
* @param buf Buffer containing FlatBuffer data
* @return Monster instance
*/
pub fn get_root(buf: &'a [u8]) -> Self {
let offset = flatbuffers::read_scalar::<u32>(&buf[0..4]) as usize;
Self {
_tab: Table::new(buf, offset),
}
}
/**
* Initialize Monster from table
* @param table Table containing Monster data
* @return Monster instance
*/
pub fn init_from_table(table: Table<'a>) -> Self {
Self { _tab: table }
}
/**
* Get position as Vec3
* @return Optional Vec3
*/
pub fn pos(&self) -> Option<Vec3> {
self._tab.get::<u32>(4).map(|offset| {
Vec3::unpack(self._tab.buf, self._tab.loc + offset as usize)
})
}
/**
* Get mana value
* @return Mana value with default
*/
pub fn mana(&self) -> i16 {
self._tab.get_with_default(6, 150)
}
/**
* Get HP value
* @return HP value with default
*/
pub fn hp(&self) -> i16 {
self._tab.get_with_default(8, 100)
}
/**
* Get name string
* @return Optional name string
*/
pub fn name(&self) -> Option<&'a str> {
self._tab.get_string(10)
}
/**
* Get inventory vector
* @return Optional inventory vector
*/
pub fn inventory(&self) -> Option<Vector<'a, u8>> {
self._tab.get_vector(14)
}
}
// Monster builder functions
pub struct MonsterArgs<'a> {
pub pos: Option<&'a Vec3>,
pub mana: i16,
pub hp: i16,
pub name: Option<WIPOffset<&'a str>>,
pub inventory: Option<WIPOffset<Vector<'a, u8>>>,
}
impl<'a> Default for MonsterArgs<'a> {
fn default() -> Self {
Self {
pos: None,
mana: 150,
hp: 100,
name: None,
inventory: None,
}
}
}
/**
* Create Monster table
* @param builder FlatBuffer builder
* @param args Monster arguments
* @return Offset to created Monster
*/
pub fn create_monster<'a>(
builder: &mut FlatBufferBuilder<'a>,
args: &MonsterArgs<'a>,
) -> WIPOffset<Monster<'a>> {
builder.start_table(6);
if let Some(pos) = args.pos {
let pos_offset = Vec3::pack(builder, pos);
builder.push_slot_always(0, pos_offset);
}
builder.push_slot(1, args.mana, 150);
builder.push_slot(2, args.hp, 100);
if let Some(name) = args.name {
builder.push_slot_always(3, name);
}
if let Some(inventory) = args.inventory {
builder.push_slot_always(5, inventory);
}
builder.end_table()
}Rust FlatBuffers provides comprehensive error handling and buffer validation.
use flatbuffers::{InvalidFlatbuffer, Verifiable, Verifier, VerifierOptions};
/**
* Error types for FlatBuffer operations
*/
#[derive(Debug, Clone)]
pub enum FlatBufferError {
InvalidBuffer(String),
OutOfBounds { index: usize, len: usize },
InvalidUtf8(std::str::Utf8Error),
VerificationFailed(InvalidFlatbuffer),
}
impl std::fmt::Display for FlatBufferError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidBuffer(msg) => write!(f, "Invalid buffer: {}", msg),
Self::OutOfBounds { index, len } => {
write!(f, "Index {} out of bounds for length {}", index, len)
}
Self::InvalidUtf8(err) => write!(f, "Invalid UTF-8: {}", err),
Self::VerificationFailed(err) => write!(f, "Verification failed: {:?}", err),
}
}
}
impl std::error::Error for FlatBufferError {}
/**
* Safe buffer access with error handling
* @param buf Buffer to validate and access
* @return Result containing Monster or error
*/
pub fn safe_get_monster(buf: &[u8]) -> Result<Monster<'_>, FlatBufferError> {
// Basic validation
if buf.len() < 4 {
return Err(FlatBufferError::InvalidBuffer("Buffer too short".to_string()));
}
// Get root with verification
let monster = Monster::get_root(buf);
// Additional validation can be added here
Ok(monster)
}
/**
* Verify buffer integrity
* @param buf Buffer to verify
* @return Result indicating success or verification error
*/
pub fn verify_buffer(buf: &[u8]) -> Result<(), FlatBufferError> {
let opts = VerifierOptions::default();
let mut verifier = Verifier::new(&opts, buf);
// Verify the buffer structure
Monster::run_verifier(&mut verifier, 0)
.map_err(FlatBufferError::VerificationFailed)
}Working with files and Rust's serialization ecosystem.
use std::fs::File;
use std::io::{Read, Write, BufReader, BufWriter};
use std::path::Path;
/**
* Save FlatBuffer to file
* @param builder Completed FlatBuffer builder
* @param path Output file path
* @return Result indicating success or IO error
*/
pub fn save_flatbuffer<P: AsRef<Path>>(
builder: &FlatBufferBuilder,
path: P,
) -> std::io::Result<()> {
let mut file = BufWriter::new(File::create(path)?);
file.write_all(builder.finished_data())?;
file.flush()
}
/**
* Load FlatBuffer from file
* @param path Input file path
* @return Result containing buffer data or IO error
*/
pub fn load_flatbuffer<P: AsRef<Path>>(path: P) -> std::io::Result<Vec<u8>> {
let mut file = BufReader::new(File::open(path)?);
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(buffer)
}
// Serde integration (with "serialize" feature)
#[cfg(feature = "serialize")]
mod serde_support {
use serde::{Deserialize, Serialize};
use super::*;
/**
* Wrapper for serde serialization of FlatBuffer data
*/
#[derive(Serialize, Deserialize)]
pub struct SerializableMonster {
pub name: Option<String>,
pub hp: i16,
pub mana: i16,
pub pos: Option<Vec3>,
pub inventory: Vec<u8>,
}
impl<'a> From<Monster<'a>> for SerializableMonster {
fn from(monster: Monster<'a>) -> Self {
Self {
name: monster.name().map(|s| s.to_string()),
hp: monster.hp(),
mana: monster.mana(),
pos: monster.pos(),
inventory: monster.inventory()
.map(|v| v.iter().collect())
.unwrap_or_default(),
}
}
}
}Complete Usage Example:
use flatbuffers::FlatBufferBuilder;
// Import generated types
use crate::generated::{Monster, MonsterArgs, Vec3, create_monster};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create monster
let mut builder = FlatBufferBuilder::new();
// Create components
let name = builder.create_string("Dragon");
let pos = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
let inventory_data = vec![1u8, 2, 3, 4, 5];
let inventory = builder.create_vector(&inventory_data);
// Build monster using args struct
let monster_args = MonsterArgs {
pos: Some(&pos),
mana: 200,
hp: 150,
name: Some(name),
inventory: Some(inventory),
..Default::default()
};
let monster = create_monster(&mut builder, &monster_args);
// Finish buffer
builder.finish(monster, None);
let buffer = builder.finished_data();
// Read the data back
let read_monster = Monster::get_root(buffer);
println!("Name: {:?}", read_monster.name());
println!("HP: {}", read_monster.hp());
println!("Mana: {}", read_monster.mana());
if let Some(position) = read_monster.pos() {
println!("Position: {}, {}, {}", position.x, position.y, position.z);
}
if let Some(inventory) = read_monster.inventory() {
println!("Inventory size: {}", inventory.len());
for (i, item) in inventory.iter().enumerate() {
println!("Item {}: {}", i, item);
}
}
// Save to file
std::fs::write("monster.bin", buffer)?;
// Load from file
let loaded_buffer = std::fs::read("monster.bin")?;
let loaded_monster = Monster::get_root(&loaded_buffer);
println!("Loaded name: {:?}", loaded_monster.name());
Ok(())
}