Skip to content

[Rust] Proposal for non-owning FlatBufferBuilder API #7385

@bsilver8192

Description

@bsilver8192

I'd like to use a FlatBufferBuilder directly into an externally-allocated buffer from Rust.

Some context: I'm looking to implement the equivalent of this C++ API. It's an interface to a shared memory IPC system which builds a table in a region of shared memory, before handing off ownership to other processes (after the table is completed).

The C++ version has a flatbuffers::Allocator implementation which aborts if you do anything besides a single allocate call. Using the initial_size argument to FlatBufferBuilder's constructor results in the FlatBufferBuilder allocating that single piece of memory and then filling it in, and if you try building a table that's too large it aborts in the custom Allocator implementation with an error message pointing to the size that needs to be increased.

This is part of what's needed for #7005.

Proposed API:

/// TODO: Does the memory need to be zeroed?
trait Allocator: Index<SliceIndex<[u8]>> + IndexMut<SliceIndex<[u8]>> {
    /// Grows the buffer, with the old contents being moved to the end.
    ///
    /// Returns the amount it has grown by, so the caller can update offsets.
    fn grow_downwards(&mut self) -> usize;
    fn len(&self) -> usize;
}

struct DefaultAllocator {
    owned_buf: Vec<u8>,
}
/// len, Index, and IndexMut forward to owned_buf.
/// grow_downwards is the current FlatBufferBuilder::grown_owned_buf, except the self.used_space()
/// and self.head operations which stay in grow_owned_buf.

pub struct FlatBufferBuilder<A: Allocator, 'fbb> {
    buf: A,
    <everything else the same>
}

/// Not sure if this works directly, or if it needs a marker trait that only DefaultAllocator implements.
impl<A: DefaultAllocator, 'fbb> FlatBufferBuilder<A, 'fbb> {
    pub fn with_capacity(size: usize) -> Self {
        Self::with_capacity_and_allocator(size, DefaultAllocator::new())
    }
    /// `new` moves here too.
}

A more direction translation of the C++ API would be something like this:

enum SomeAllocator {
    Default(Vec<u8>),
    Dyn(Box<dyn Allocator>),
}

but I don't think that makes sense in Rust. Rust's generics are easier to work with than C++, so doing it with the trait directly means users can have a custom allocator without any vtables if desired. It also allows the custom type to carry lifetimes with it, which I want for my use case.

I'm prepared to implement this and send a PR. Thoughts on the API before I start doing that?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions