Search code examples
rusttraitslifetimelifetime-scoping

Can a trait's impl specify a lifetime that comes from a method's input argument?


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


Solution

  • 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)