Tracking lifetimes accross function traits

Hello! I am having issues with annotating the lifetimes of a function that takes two impl FnOnce as arguments. This is my use case:

builder.then_build(literal("next"), |builder| {
    builder.command(…)
});

The builder is a structure holding a mutable reference to a TreeGraph structure.
And literal is a function returning impl FnOnce which takes a &'a mut TreeGraph and returns a new builder structure which is holding the given reference.

This is my original implementation of then_build:

pub fn then_build<'a, 'b, FB, B, F>(&'a mut self, builder: FB, scope: F)
where
    FB: FnOnce(&'b mut TreeGraph<CS, AT, SP, R, M, S, CR>) -> B,
    B: Build<Node = CommandNode<CS, AT, SP, R, M, S, CR>> + 'b,
    F: FnOnce(&mut B),
    'a: 'b,
{
    let node = {
        let mut b = builder(self.tree);
        scope(&mut b);
        b.build()
    };
    self.then(node); // <- Error
}

But I can't borrow self because self.tree is still borrowed mutably by builder(self.tree).

For more context: This is part of a port of Mojang's Brigadier Java library to Rust; the ArgumentBuilder Class to be precise.

My idea was that the builder function creates a new builder structure for a kind of node in the graph. This builder borrows the tree of the parent builder mutably. The scope function then modifies this builder.

When I don't give any lifetimes, the function has no errors, but I cannot use the function because the builder function cannot return a structure that is storing the given mutable tree reference.


It was recommended to me that I should try something similar to AsyncFnOnce1 in async_fn_traits - Rust, but I cannot get it to work.

I first started this discussion on the :gear: Rust Community Discord.

It's not possible to tell what a possible fix would be without the missing definitions of Build, builder, and TreeGraph (etc.). Could you please share those too and create a minimal example reproducing the error?

If you need the borrow in the call to builder to be shorter than your function body, that's a lifetime too short for caller to be able to name. The way you spell that is to say "you can take any lifetime" with a higher-ranked trait bound.

// First attempt
FB: FnOnce(&mut TreeGraph<CS, AT, SP, R, M, S, CR>) -> B,
// AKA
FB: for<'any> FnOnce(&'any mut TreeGraph<CS, AT, SP, R, M, S, CR>) -> B,

Your next problem will then be that return value cannot borrow from the input in this pattern, because type parameters like B must represent a single type, whereas types that vary only by lifetime are still distinct types. At this point you'd need some helper traits to make progress.

// Spitballing as I don't have a runnable example to check against
trait FbOnce<'fb, _Bound = &'fb Self>
where
    Self: FnOnce(&'fb mut ...) -> <Self as FbOnce<'fb>>::Out
{
    type Out: 'b + Build<...>
}

// A single result type is ok for a single input lifetime
impl<'fb, FB: FnOnce(...) -> B, B: 'fb + Build> FbOnce<...> for FB {
    type Out = B;
}

// Note how you don't have to mention the output with a type parameter now
trait FbAny: for<'any> FbOnce<'any> {}

impl <T: for<'any> FbOnce<'any>> FbAny for T {}

// Then use it
pub fn then_build<FB, F>(&mut self, builder: FB, scope: F)
where
    FB: FbAny,
    F: FnOnce(&mut B),
{ /* ... */ }

This is a minimal complete example:

fn main() {
    let mut tree = Tree {
        something: "foo",
        number: 2,
    };
    let mut builder = ArgumentBuilder {
        tree: &mut tree,
        state: 42,
    };
    builder.then_build(something(1337), |b| {
        //
    });
    println!("{}", builder.build());
}

struct Tree<X> {
    something: X,
    number: usize,
    // …
}

struct ArgumentBuilder<'t, X> {
    tree: &'t mut Tree<X>,
    state: usize,
    // …
}

impl<'t, X> ArgumentBuilder<'t, X> {
    fn then_build<'a, 'b, FB, B, F>(&'a mut self, builder: FB, scope: F)
    where
        FB: FnOnce(&'b mut Tree<X>) -> B,
        B: Build + 'b,
        F: FnOnce(&mut B),
        'a: 'b,
    {
        let node = {
            let mut b = builder(self.tree);
            scope(&mut b);
            b.build()
        };
        self.state += node * self.tree.number; // <- Error
    }
}

trait Build {
    fn build(self) -> usize;
}

impl<'t, X> Build for ArgumentBuilder<'t, X> {
    fn build(self) -> usize {
        self.state
    }
}

fn something<'a, X>(state: usize) -> impl FnOnce(&'a mut Tree<X>) -> ArgumentBuilder<'a, X> {
    return move |tree| ArgumentBuilder { tree, state };
}

Thank you, this partially solved my problem. I added this trait and I'm sorry for the mess, I don't know how to conveniently reduce the number of generic type parameters:

pub trait BuilderFactory<'b, CS: 'b, AT: 'b, SP: 'b, R: 'b, M: 'b, S: 'b, CR: 'b>
where
    Self: FnOnce(&'b mut TreeGraph<CS, AT, SP, R, M, S, CR>) -> Self::Builder,
    AT: ArgumentType,
{
    type Builder: Build<Node = CommandNode<CS, AT, SP, R, M, S, CR>> + 'b;
}

impl<'b, F, B, CS: 'b, AT: 'b, SP: 'b, R: 'b, M: 'b, S: 'b, CR: 'b>
    BuilderFactory<'b, CS, AT, SP, R, M, S, CR> for F
where
    F: FnOnce(&'b mut TreeGraph<CS, AT, SP, R, M, S, CR>) -> B,
    B: Build<Node = CommandNode<CS, AT, SP, R, M, S, CR>> + 'b,
    AT: ArgumentType,
{
    type Builder = B;
}

And then I modified then_build:

pub fn then_build<FB, F>(&mut self, builder: FB, scope: F)
where
    FB: for<'b> BuilderFactory<'b, CS, AT, SP, R, M, S, CR>,
    F: for<'b> FnOnce(&mut <FB as BuilderFactory<'b, CS, AT, SP, R, M, S, CR>>::Builder),
{
    let node = {
        let mut b = builder(self.tree);
        scope(&mut b);
        b.build()
    };
    self.then(node);
}

But now Rust doesn't correctly detect the lifetimes when I use the function:

builder.then_build(literal("xyz"), |b| {});
one type is more general than the other Note: expected trait `for<'r, 'b> FnOnce<(&'r mut LiteralArgumentBuilder<'b, (), Type, (), Unrestricted, NoRedirect, String, usize>,)>` found trait `for<'r> FnOnce<(&'r mut LiteralArgumentBuilder<'_, (), Type, (), Unrestricted, NoRedirect, String, usize>,)>`

But this works:

builder.then_build(literal("xyz"), take_it);

fn take_it<'a, 'b>(
    b: &'a mut LiteralArgumentBuilder<'b, (), Type, (), Unrestricted, NoRedirect, String, usize>,
) {
    //
}

You may be running into Rust's poor higher-ranked inference for closures. Sometimes this is enough to fix it:

builder.then_build(literal("xyz"), |b: &mut _| {});
//                                   ^^^^^^^^

But sometimes other hacky ways are needed like

// Maybe this could be a method on a trait
//    HrBuilderFactor: for<'any> BuilderFactory<...>
fn force_hr<F: for<'b> FnOnce(...)>(f: F) -> F { f }
// ...
builder.then_build(literal("xyz"), force_hr(|b| {}));

If the inference difficulties are actually coming from the new trait and implementations, perhaps the mitigating approaches taken in this thread will be of use to you. But note that in that thread as well, the poor inference of higher-ranked closures remained. There's an accepted RFC for a more ergonomic workaround for that, but it's not yet implemented stable.

(Edit: It is implemented (example), the tracking issue is just failing to track the issue.)

I tried this out, but it does not help, sadly. I guess I'll need to wait until the Language is ready for this, it's so frustrating.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.