rust: add secondary function with preallocated internal vecs (#8936)

* rust: add secondary function with preallocated internal vecs

* docs: document pre allocation feature for rust implementation
diff --git a/docs/source/languages/rust.md b/docs/source/languages/rust.md
index 04f7f02..6a8187b 100644
--- a/docs/source/languages/rust.md
+++ b/docs/source/languages/rust.md
@@ -205,6 +205,47 @@
 - Safe getters in [SafeBuffer](https://docs.rs/flatbuffers-reflection/latest/flatbuffers_reflection/struct.SafeBuffer.html),
  which does verification when constructed so you can use it for any data source
 
+## Buffer pre allocation in a latency-sensitive context
+
+In latency-sensitive applications, dynamic memory allocations can introduce unpredictable latency spikes. The `FlatBufferBuilder` internally uses several `Vec`s that may reallocate during serialization:
+
+- The backing buffer for the FlatBuffer data
+- `field_locs` for tracking field locations within tables
+- `written_vtable_revpos` for deduplicating vtables
+- `strings_pool` for shared string interning
+
+To avoid allocations during serialization, you can preallocate all internal vectors upfront using the `with_internal_capacity` constructor:
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.rs}
+    // Preallocate: 1KB buffer, 8 field locations, 16 vtables, 32 shared strings
+    let mut builder = FlatBufferBuilder::with_internal_capacity(1024, 8, 16, 32);
+
+    // All subsequent operations will not allocate (if capacities are sufficient)
+    let name = builder.create_shared_string("MyMonster");
+    // ... build your FlatBuffer ...
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are three variants available:
+
+- `with_internal_capacity(size, field_locs, vtables, strings)` - Creates a new builder with all capacities preallocated
+- `from_vec_with_internal_capacity(buffer, field_locs, vtables, strings)` - Reuses an existing `Vec<u8>` as the backing buffer
+- `new_in_with_internal_capacity(allocator, field_locs, vtables, strings)` - Uses a custom `Allocator` with preallocated internal vecs
+
+When combined with `reset()`, you can reuse the same builder across multiple serializations without any allocations after the initial setup:
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.rs}
+    let mut builder = FlatBufferBuilder::with_internal_capacity(1024, 8, 16, 32);
+
+    loop {
+        // Build a FlatBuffer (allocation-free if capacities are sufficient)
+        let data = build_message(&mut builder);
+        send(data);
+
+        // Reset for reuse - clears state but retains allocated capacity
+        builder.reset();
+    }
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
 ## Useful tools created by others
 
 * [flatc-rust](https://github.com/frol/flatc-rust) - FlatBuffers compiler
diff --git a/rust/flatbuffers/src/builder.rs b/rust/flatbuffers/src/builder.rs
index e6b9cfb..f4cf961 100644
--- a/rust/flatbuffers/src/builder.rs
+++ b/rust/flatbuffers/src/builder.rs
@@ -166,6 +166,60 @@
     pub fn with_capacity(size: usize) -> Self {
         Self::from_vec(vec![0; size])
     }
+    /// Create a FlatBufferBuilder that is ready for writing, with a
+    /// ready-to-use capacity of the provided size and preallocated internal vecs.
+    ///
+    /// The maximum valid value for `size` is `FLATBUFFERS_MAX_BUFFER_SIZE`.
+    ///
+    /// # Arguments
+    ///
+    /// * `size` - The initial capacity of the backing buffer in bytes.
+    /// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
+    /// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
+    /// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
+    pub fn with_internal_capacity(
+        size: usize,
+        field_locs_capacity: usize,
+        written_vtable_revpos_capacity: usize,
+        strings_pool_capacity: usize,
+    ) -> Self {
+        Self::from_vec_with_internal_capacity(
+            vec![0; size],
+            field_locs_capacity,
+            written_vtable_revpos_capacity,
+            strings_pool_capacity,
+        )
+    }
+    /// Create a FlatBufferBuilder that is ready for writing, reusing
+    /// an existing vector and preallocated internal vecs.
+    ///
+    /// # Arguments
+    ///
+    /// * `buffer` - An existing `Vec<u8>` to reuse as the backing buffer.
+    /// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
+    /// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
+    /// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
+    pub fn from_vec_with_internal_capacity(
+        buffer: Vec<u8>,
+        field_locs_capacity: usize,
+        written_vtable_revpos_capacity: usize,
+        strings_pool_capacity: usize,
+    ) -> Self {
+        // we need to check the size here because we create the backing buffer
+        // directly, bypassing the typical way of using grow_allocator:
+        assert!(
+            buffer.len() <= FLATBUFFERS_MAX_BUFFER_SIZE,
+            "cannot initialize buffer bigger than 2 gigabytes"
+        );
+        let allocator = DefaultAllocator::from_vec(buffer);
+        Self::new_in_with_internal_capacity(
+            allocator,
+            field_locs_capacity,
+            written_vtable_revpos_capacity,
+            strings_pool_capacity,
+        )
+    }
+
     /// Create a FlatBufferBuilder that is ready for writing, reusing
     /// an existing vector.
     pub fn from_vec(buffer: Vec<u8>) -> Self {
@@ -212,6 +266,40 @@
         }
     }
 
+    /// Create a [`FlatBufferBuilder`] that is ready for writing with a custom [`Allocator`]
+    /// and preallocated internal vecs.
+    ///
+    /// # Arguments
+    ///
+    /// * `allocator` - A custom [`Allocator`] to use as the backing buffer.
+    /// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
+    /// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
+    /// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
+    pub fn new_in_with_internal_capacity(
+        allocator: A,
+        field_locs_capacity: usize,
+        written_vtable_revpos_capacity: usize,
+        strings_pool_capacity: usize,
+    ) -> Self {
+        let head = ReverseIndex::end();
+        FlatBufferBuilder {
+            allocator,
+            head,
+
+            field_locs: Vec::with_capacity(field_locs_capacity),
+            written_vtable_revpos: Vec::with_capacity(written_vtable_revpos_capacity),
+
+            nested: false,
+            finished: false,
+
+            min_align: 0,
+            force_defaults: false,
+            strings_pool: Vec::with_capacity(strings_pool_capacity),
+
+            _phantom: PhantomData,
+        }
+    }
+
     /// Destroy the [`FlatBufferBuilder`], returning its [`Allocator`] and the index
     /// into it that represents the start of valid data.
     pub fn collapse_in(self) -> (A, usize) {
@@ -232,7 +320,9 @@
     /// new object.
     pub fn reset(&mut self) {
         // memset only the part of the buffer that could be dirty:
-        self.allocator[self.head.range_to_end()].iter_mut().for_each(|x| *x = 0);
+        self.allocator[self.head.range_to_end()]
+            .iter_mut()
+            .for_each(|x| *x = 0);
 
         self.head = ReverseIndex::end();
         self.written_vtable_revpos.clear();
@@ -961,6 +1051,33 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use core::sync::atomic::{AtomicUsize, Ordering};
+    use std::alloc::{GlobalAlloc, Layout, System};
+
+    static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
+
+    struct CountingAllocator;
+
+    unsafe impl GlobalAlloc for CountingAllocator {
+        unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+            ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
+            unsafe { System.alloc(layout) }
+        }
+        unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
+            unsafe { System.dealloc(ptr, layout) }
+        }
+    }
+
+    #[global_allocator]
+    static GLOBAL: CountingAllocator = CountingAllocator;
+
+    fn reset_alloc_count() {
+        ALLOC_COUNT.store(0, Ordering::Relaxed);
+    }
+
+    fn alloc_count() -> usize {
+        ALLOC_COUNT.load(Ordering::Relaxed)
+    }
 
     #[test]
     fn reverse_index_test() {
@@ -970,4 +1087,43 @@
         assert_eq!(&buf[idx.range_to(idx + 1)], &[4]);
         assert_eq!(idx.to_forward_index(&buf), 4);
     }
+
+    #[test]
+    fn with_internal_capacity_preallocates_vecs() {
+        let mut builder = FlatBufferBuilder::with_internal_capacity(64, 8, 16, 32);
+
+        assert!(builder.allocator.len() >= 64);
+        assert!(builder.field_locs.capacity() >= 8);
+        assert!(builder.written_vtable_revpos.capacity() >= 16);
+        assert!(builder.strings_pool.capacity() >= 32);
+
+        assert!(builder.field_locs.is_empty());
+        assert!(builder.written_vtable_revpos.is_empty());
+        assert!(builder.strings_pool.is_empty());
+
+        // Reset the allocation counter after builder construction
+        reset_alloc_count();
+
+        // Add a shared string and verify it lands in the pool
+        let s1 = builder.create_shared_string("hello");
+        assert_eq!(builder.strings_pool.len(), 1);
+
+        // Adding the same string again should reuse the pooled entry
+        let s2 = builder.create_shared_string("hello");
+        assert_eq!(builder.strings_pool.len(), 1);
+        assert_eq!(s1.value(), s2.value());
+
+        // A different string should add a new entry
+        let _s3 = builder.create_shared_string("world");
+        assert_eq!(builder.strings_pool.len(), 2);
+
+        // With sufficient preallocated capacity, no additional allocations
+        // should have occurred for the internal vecs during the operations above
+        let allocs = alloc_count();
+        assert_eq!(
+            allocs, 0,
+            "expected 0 allocations after builder construction, got {}",
+            allocs
+        );
+    }
 }