0

For a type

pub struct Child<'a> {
    buf: &'a mut [u8],
}

I can define a trait and implement the trait for the type but with a lifetime that is bound to a calling function's context (not to a local loop context):

pub trait MakeMut<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self;
}

impl<'a> MakeMut<'a> for Child<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self {
        Self { buf }
    }
}

And first to show a somewhat working example because x is only borrowed within the context of the loop because Child::make_mut is hardcoded in the map1 function:

pub fn map1<F>(mut func: F)
    where
        F: FnMut(&mut Child),
{
    let mut vec = vec![0; 16];
    let x = &mut vec;
    for i in 0..2 {
        let offset = i * 8;
        let s =  &mut x[offset..];
        let mut w = Child::make_mut(s);
        func(&mut w);
    }
}

But in trying to make map2, a generic version of map1 where the T is bound to the MakeMut trait but with lifetime of the entire function body, this won't compile, for good reasons (the T lifetimes that would be created by T: MakeMut<'a> have the lifetime of map2, not the inner loop):

pub fn map2<'a, F, T>(mut func: F)   // lifetime `'a` defined here
    where
        T: MakeMut<'a>,
        F: FnMut(&mut T),
{
    let mut vec = vec![0; 16];
    let x = &mut vec;
    for i in 0..2 {
        let offset = i * 8;
        let s =  &mut x[offset..];
        let mut w = T::make_mut(s);  // error: argument requires that `*x` is borrowed for `'a`
        func(&mut w);
    }
}

I want to do something almost like this but of course it doesn't compile either:

pub trait MakeMut {
    fn make_mut<'a>(buf: &'a mut [u8]) -> Self;
}

impl<'a> MakeMut for Child<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self {    // lifetime mismatch
        Self{ buf }
    }
}

with the compiler errors:

error[E0308]: method not compatible with trait
  --> src/main.rs:45:5
   |
45 |     fn make_mut(buf: &'a mut [u8]) -> Self {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected fn pointer `fn(&'a mut [u8]) -> Child<'_>`
              found fn pointer `fn(&'a mut [u8]) -> Child<'_>`
note: the lifetime `'a` as defined here...
  --> src/main.rs:45:5
   |
45 |     fn make_mut(buf: &'a mut [u8]) -> Self {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime `'a` as defined here
  --> src/main.rs:44:6
   |
44 | impl<'a> MakeMut for Child<'a> {
   |      ^^

Is there a syntax that allows a trait for a Child<'a> where the 'a is defined by the input argument to the method make_mut? So a generic function could be defined for a trait that returns an instance but where the instance lifetime is not the entire function, but just a shorter lifetime defined by an inner block?

I understand the lifetime is part of the type being returned, but it almost seems like a higher-ranked trait bound (HRTB) would suite this problem except I haven't found a way to specify the lifetime that suites the trait and the method signatures.

Here is a playground link https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fb28d6da9d89fde645edeb1ca0ae5b21

WeakPointer
  • 3,087
  • 27
  • 22
  • I've tried a few variations and I don't think this can work with `Child` -- the `F` argument requires a specific `T`, but the caller cannot make `T` be `Child` without having a specific lifetime on `Child`, which will bind the lifetime to an external lifetime no matter what you do. I don't think this can work without generic closures, which Rust does not have. Using a HRTB to bound `T` solves half of the problem, but to solve the other half you need to be able to specify a generic closure that accepts `&mut Child<'a>` _for any `'a`_. – cdhowie Jun 23 '22 at 16:01
  • @cdhowie [It works with HRTB](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=84636e1fa3e9042ba163781994355022) (and I don't understand why you deleted your answer). – Chayim Friedman Jun 23 '22 at 16:03
  • 2
    @ChayimFriedman I deleted it because [you can't actually call `map2` with `T=Child<'_>`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a2730f716c1e1538c7ad6cebdb0413ee) for the above reason -- the closure mandates a specific lifetime argument to `Child`, which infects backwards from `func(&mut w)` in `map2`. Basically there isn't a way for the closure itself to be generic over the lifetime argument to `Child`, which is needed here for the lifetimes to agree. – cdhowie Jun 23 '22 at 16:05
  • @ChayimFriedman I'm not even sure generic closures would work here because the caller has to decide on a specific `T` which in the case of `Child` will have to have a specific lifetime. I think the problem here is coupling `MakeMut` together with `Child` and having `MakeMut` refer to `Self`. Binding them together requires binding the lifetimes together, which is what creates the problem. The caller needs a way to specify a `T` with no lifetime, that can produce `Child` with any lifetime. – cdhowie Jun 23 '22 at 16:18
  • 1
    @cdhowie Yes, that is the problem. I think GATs may help here, but I have to check. – Chayim Friedman Jun 23 '22 at 16:22
  • 1
    [Here's a beautiful(?) example that works with GATs](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=31a5c4b2fd762e571c286c0bbbedde8e). – Chayim Friedman Jun 23 '22 at 16:24
  • 2
    @ChayimFriedman [I made it work without GATs](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d5ff26066bb0c9df935760eb73366624) by decoupling `MakeMut` from `Child`. Going to revive my answer with this approach. – cdhowie Jun 23 '22 at 16:27
  • @cdhowie That is very interesting. You created a trait with a lifetime that can be satisfied with an HRTB. I hadn't learned why one would use a type within a trait definition although I had seen them used in traits related to iterators. And the anonymous function type you pass to map2 on line 2, I'll have to wrap my head around that one too. Let me work with these ideas a bit and I'll get back. Thank you both. – WeakPointer Jun 23 '22 at 16:50
  • @cdhowie These scheme, using a factory struct, has worked very well in my larger real life example. Big thank you to you both. So taking this a little past an academic exercise, here is a fuller example of a Parent struct and method that uses the technique from here to implement an `apply` method that works on a borrowed mutable reference in a loop. [fuller example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b9f5c1ef5dfa618edc991870c64b2d89) – WeakPointer Jun 23 '22 at 19:41

1 Answers1

3

Your first attempt is close to what you want. For reference:

pub trait MakeMut<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self;
}

impl<'a> MakeMut<'a> for Child<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self {
        Self { buf }
    }
}

The first problem is the bound on T in map2:

pub fn map2<'a, F, T>(mut func: F)
where
    T: MakeMut<'a>,
    F: FnMut(&mut T),

This requires the compiler to deduce a single 'a that applies for the whole function. Since lifetime parameters come from outside of the function, the lifetime 'a is necessarily longer than the function invocation, which means anything with lifetime 'a has to outlive the function. Working backwards from the T::make_mut() call, the compiler eventually deduces that x is &'a mut Vec<_> which means vec has to outlive the function invocation, but there's no possible way it can since it's a local.

This can be fixed by using a higher-rank trait bound indicating that T has to implement MakeMut<'a> for any possible lifetime 'a, which is expressed like this:

pub fn map2<F, T>(mut func: F)
where
    T: for<'a> MakeMut<'a>,
    F: FnMut(&mut T),

With this change, the code compiles.

What you'll then find is that you can't ever actually call map2 with T=Child<'_> because you'll run into the same problem in a different place. The caller must specify a specific lifetime for 'a in Child<'a>, but this disagrees with the HRTB -- you have impl<'a> MakeMut<'a> for Child<'a> but the HRTB wants impl<'a, 'b> MakeMut<'b> for Child<'a>, and that brings back the lifetime problem in that implementation's make_mut.

One way around this is to decouple the implementation of MakeMut from Child, providing a "factory type" that uses associated types. This way, the caller doesn't have to supply any pesky lifetime argument that could cause trouble later.

pub trait MakeMut<'a> {
    type Item;
    fn make_mut(buf: &'a mut [u8]) -> Self::Item;
}

struct ChildFactory;

impl<'a> MakeMut<'a> for ChildFactory {
    type Item = Child<'a>;
    fn make_mut(buf: &'a mut [u8]) -> Child<'a> {
        Child { buf }
    }
}

Then we modify map2 to be aware of the associated type:

pub fn map2<F, T>(mut func: F)
where
    T: for<'a> MakeMut<'a>,
    F: for<'a, 'b> FnMut(&'b mut <T as MakeMut<'a>>::Item),

whew

Now, finally, we can use map2:

map2::<_, ChildFactory>(|v| {});

(Playground)

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Super helpful and right on point. So a big thank you again. I've created a [fuller example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b9f5c1ef5dfa618edc991870c64b2d89) that uses your mechanism, showing how the solution benefits a more typical Parent struct that wants to share a mutable resource with multiple Children, one at a time. And of course the point was that this sharing mechanism only needs to be coded once as a generic type so a system that uses multiple types of Parent and types of Child can be easier to write and later read. – WeakPointer Jun 23 '22 at 19:50