Can't figure out why helper function for rust-capnp holds borrow reference too long

141 Views Asked by At

I'm trying to create a helper method to rust-capnp that takes a initialization function and returns a Vec<u8> that contains a serialized version of the message.

fn construct_serialized_capnp_into<'a, 'b, A, T, F>(
    mut builder: capnp::message::Builder<A>,
    init: F,
) -> Vec<u8>
where
    'a: 'b,
    A: for<'c> capnp::message::Allocator + 'a,
    T: capnp::traits::FromPointerBuilder<'b> + 'b,
    F: FnOnce(&'b mut T),
{
    let serialized_length = {
      let mut root = builder.init_root::<T>();
        init(&mut root);
        capnp::serialize::compute_serialized_size_in_words(&builder)
             * std::mem::size_of::<capnp::Word>()
    };
    let mut buf = Vec::with_capacity(serialized_length);
    buf.resize(serialized_length, 0);
    capnp::serialize::write_message(Cursor::new(buf.as_mut_slice()), &builder).unwrap();
    buf
}

pub(crate) fn construct_serialized_capnp<'a, 'b, T, F>(
    dest: &DmaFile,
    scratch_space: Option<&'a mut [u8]>,
    init: F,
) -> Vec<u8>
where
    'a: 'b,
    T: capnp::traits::FromPointerBuilder<'b> + 'b,
    F: FnOnce(&'b mut T),
{
    match scratch_space {
        Some(space) => construct_serialized_capnp_into(
            capnp::message::Builder::new(capnp::message::ScratchSpaceHeapAllocator::new(space)),
            init,
        ),
        None => construct_serialized_capnp_into(capnp::message::Builder::new_default(), init),
    }
}

This fails on 4 lines. init_root fails with:

76 | fn construct_serialized_capnp_into<'a, 'b, A, T, F>(
   |                                        -- lifetime `'b` defined here
...
87 |       let mut root = builder.init_root::<T>();
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^
   |                      |
   |                      borrowed value does not live long enough
   |                      argument requires that `builder` is borrowed for `'b`
...
95 | }
   | - `builder` dropped here while still borrowed

Invoking the init function fails with:

76 | fn construct_serialized_capnp_into<'a, 'b, A, T, F>(
   |                                        -- lifetime `'b` defined here
...
88 |         init(&mut root);
   |         -----^^^^^^^^^-
   |         |    |
   |         |    borrowed value does not live long enough
   |         argument requires that `root` is borrowed for `'b`
...
91 |     };
   |     - `root` dropped here while still borrowed

And then two more about trying to borrow builder immutably because (as above indicates) I'm not getting Rust to interpret the borrow lifetime correctly:

error[E0502]: cannot borrow `builder` as immutable because it is also borrowed as mutable
   |
76 | fn construct_serialized_capnp_into<'a, 'b, A, T, F>(
   |                                        -- lifetime `'b` defined here
...
87 |       let mut root = builder.init_root::<T>();
   |                      ------------------------
   |                      |
   |                      mutable borrow occurs here
   |                      argument requires that `builder` is borrowed for `'b`
88 |         init(&mut root);
89 |         capnp::serialize::compute_serialized_size_in_words(&builder)
   |                                                            ^^^^^^^^ immutable borrow occurs here

error[E0502]: cannot borrow `builder` as immutable because it is also borrowed as mutable
   |
76 | fn construct_serialized_capnp_into<'a, 'b, A, T, F>(
   |                                        -- lifetime `'b` defined here
...
87 |       let mut root = builder.init_root::<T>();
   |                      ------------------------
   |                      |
   |                      mutable borrow occurs here
   |                      argument requires that `builder` is borrowed for `'b`
...
93 |     capnp::serialize::write_message(Cursor::new(buf.as_mut_slice()), &builder).unwrap();
   |                                                                      ^^^^^^^^ immutable borrow occurs here

Can any Rust/rust-capnp expert help me understand how to fix this (or confirm if it's impossible to implement a function like this)?

1

There are 1 best solutions below

3
On

It compiles if you get rid of all the explicit lifetime parameters:

use std::io::Cursor;

struct DmaFile {}

fn construct_serialized_capnp_into<A, T, F>(
    mut builder: capnp::message::Builder<A>,
    init: F,
) -> Vec<u8>
where
    A: for<'c> capnp::message::Allocator,
    T: for<'a> capnp::traits::FromPointerBuilder<'a>,
    F: FnOnce(&mut T),
{
    let serialized_length = {
      let mut root = builder.init_root::<T>();
        init(&mut root);
        capnp::serialize::compute_serialized_size_in_words(&builder)
             * std::mem::size_of::<capnp::Word>()
    };
    let mut buf = Vec::with_capacity(serialized_length);
    buf.resize(serialized_length, 0);
    capnp::serialize::write_message(Cursor::new(buf.as_mut_slice()), &builder).unwrap();
    buf
}

pub(crate) fn construct_serialized_capnp<T, F>(
    dest: &DmaFile,
    scratch_space: Option<&mut [u8]>,
    init: F,
) -> Vec<u8>
where
    T: for<'a> capnp::traits::FromPointerBuilder<'a>,
    F: FnOnce(&mut T),
{
    match scratch_space {
        Some(space) => construct_serialized_capnp_into(
            capnp::message::Builder::new(capnp::message::ScratchSpaceHeapAllocator::new(space)),
            init,
        ),
        None => construct_serialized_capnp_into(capnp::message::Builder::new_default(), init),
    }
}