Hi,
I'm writing a program that uses Cap'n Proto and I've created a puzzle I haven't been able to solve. I don't think this is specific to Cap'n Proto, but it led me in this direction.
Here's a self-contained snippet that works:
fn declare_message_and_init_struct_directly() {
let mut message_builder = capnp::message::Builder::new_default();
let mut struct_builder = message_builder.init_root::<my_generated_struct::Builder>();
struct_builder.set_data(b"abc123");
}
I found myself doing this multiple times with minor variations and wanted to see about rearranging it into a function that receives a closure for the part that varied. As a first step, I was able to move the init_root
call into a separate function and pass the set_data
call as a closure:
fn declare_message_directly_and_init_struct_through_closure() {
init_struct(
&mut capnp::message::Builder::new_default(),
|mut struct_builder: my_generated_struct::Builder| {
struct_builder.set_data(b"abc123");
},
);
}
fn init_struct<
'message,
A: capnp::message::Allocator,
T: capnp::traits::FromPointerBuilder<'message>,
InitFn: Fn(T),
>(
message_builder: &'message mut capnp::message::Builder<A>,
init_fn: InitFn,
) {
init_fn(message_builder.init_root::<T>());
}
That's not any better at the call site, but it was an interesting exercise to find an appropriate trait in the capnp library and figure out how to express the lifetime bounds on it. The builder must be parameterized with a lifetime that does not outlive the capnp::message::Builder
it was initially borrowed from, so I provided the function with a bound on that lifetime ('message
) by passing it a mutable reference that outlives the capnp::message:Builder
.
I then attempted to move ownership of the capnp::message::Builder
into the extracted function:
fn declare_message_and_init_struct_through_closure() {
declare_message_and_init_struct(|mut struct_builder: my_generated_struct::Builder| {
struct_builder.set_data(b"abc123");
});
}
fn declare_message_and_init_struct<
'message,
T: capnp::traits::FromPointerBuilder<'message>,
InitFn: Fn(T),
>(
init_fn: InitFn,
) {
init_struct(&mut capnp::message::Builder::new_default(), init_fn);
}
// init_struct is reused from above
That's what I want the call site to look like in the end, but this is not valid as written.
error[E0597]: borrowed value does not live long enough
--> src/lib.rs:179:26
|
179 | init_struct(&mut capnp::message::Builder::new_default(), init_fn);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value only lives until here
| |
| temporary value does not live long enough
|
note: borrowed value must be valid for the lifetime 'message as defined on the function body at 173:9..
.
--> src/lib.rs:173:9
|
173 | 'message,
| ^^^^^^^^
= note: consider using a `let` binding to increase its lifetime
I don't know how to tell the compiler that I want the lifetime parameter 'message
in my_generated_struct::Builder<'message'>
to be the same as the body of the init_fn
. (Adding a let
binding as advised only expands the insufficient lifetime to the whole body of declare_message_and_init_struct
.) The compiler seems to be interpreting 'message
as a free parameter, having no constraint relative to what's going on when I call the passed Fn
.
My specific question is how can I write a declare_message_and_init_struct
that I can use as a Fn(Fn(my_generated_struct::Builder))
for any particular generated message's Builder? I can write this easily with any concrete foo::Builder<_>
type because the compiler is happy to infer the lifetime of the struct builder within a function's body. But how do I get the same arrangement across a function call?
Thanks,
mvanbem