How to provide lifetime for a generic trait?

Given this trait from another crate:

trait ToMut<'a> {
    type Output;
    fn to_mut(&'a mut self) -> Self::Output;
}

I'd like to use it in my generic function as an intermediate step, but it's result wont leave function. So whatever lifetime I provide should be "contained" within the function, is it possible?

I start from:

struct P<T>(T);

fn init<I,A,B>(mut t: I) -> I
   where I: ToMut<Output = P<A>>,
     A: DerefMut<Target = B>,
     B: Default
{
    let mut f = t.to_mut();
    *f.0 = Default::default();
    t
}

Which fails to compile because lifetime is not provided. There are 2 options I can think of to provide one:

  1.  fn init<I,A,B>(mut t: I) -> I
         where for<'a> I: ToMut<'a, Output = P<A>>
    

    which results in the implementation of ToMut is not general enough error. Reading about it kinda make sense, in the generic bounds we ask to ToMut for any lifetime, but our implementation can do only for one concrete lifetime.

  2. Add generic lifetime parameter:

    fn init<'a, I,A,B>(mut t: I) -> I
        where I: ToMut<'a, Output = P<A>> + 'a,
    

    but then lifetime is set externally by the caller and obviosly can't be satisfied by the internal to_mut() call which result is discarded within the function.

How can I use ToMut trait in my function?

Full example: Rust Playground

The error confuses me, because I would assume that P<T> implements ToMut<'a> for every 'a. (Playground)

I think the T: 'a bound is what makes that not true. The for<'a> clause doesn't involve a reference so it doesn't limit itself to only the lifetimes where T is valid.

The “implementation … not general enough” error is because the requirement for<'a> I: ToMut<'a, Output = P<A>> disallows the Output type to depend on the lifetime 'a. How to avoid this problem, I’m still thinking about that; not the most straightforward thing – maybe introducing some helper trait(s) could work :thinking:

For the exact code at hand, the easiest solution is probably to just turn init from a I -> I function into a &mut I -> () function. Then you have an actual sensible lifetime parameter to work with.

1 Like

Without changing the I -> I property, I guess this compiles, but it still has some downsides

use std::ops::DerefMut;

trait ToMut<'a> {
    type Output;
    fn to_mut(&'a mut self) -> Self::Output;
}

struct P<T>(T);

impl<'a, T: 'a> ToMut<'a> for P<T> {
    type Output = P<&'a mut T>;
    fn to_mut(&'a mut self) -> Self::Output {
        P(&mut self.0)
    }
}

trait InitHelperTrait<'a> {
    fn init_inner_action(t: &'a mut Self) {}
}
impl<'a, I, A, B> InitHelperTrait<'a> for I
where
    I: ToMut<'a, Output = P<A>>,
    A: DerefMut<Target = B>,
    B: Default,
{
    fn init_inner_action(t: &'a mut I) {
        let mut f = t.to_mut();
        *f.0 = Default::default();
    }
}

fn init<I>(mut t: I) -> I
where
    I: for<'a> InitHelperTrait<'a>,
{
    I::init_inner_action(&mut t);
    t
}

fn test() {
    let mut p = P(99_usize);
    p = init(p);
    assert_eq!(p.0, 0);
}

in particular with the (necessary) T: 'a bound on the ToMut<'a> for P<T> implementation, the init call will only work with T: 'static, as e.g. demonstrated by how

fn test() {
    let x = [99_usize];
    let mut p = P(&x[..]);
    p = init(p);
    assert_eq!(p.0, &[0][..]);
}

would fail with

error[E0597]: `x` does not live long enough
  --> src/lib.rs:42:20
   |
42 |     let mut p = P(&x[..]);
   |                    ^ borrowed value does not live long enough
43 |     p = init(p);
   |         ------- argument requires that `x` is borrowed for `'static`
44 |     assert_eq!(p.0, &[0][..]);
45 | }
   | - `x` dropped here while still borrowed
   |
note: due to current limitations in the borrow checker, this implies a `'static` lifetime
  --> src/lib.rs:34:8
   |
34 |     I: for<'a> InitHelperTrait<'a>,
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^

whereas the &mut I-approach works just fine even with that kind of use-case

use std::ops::DerefMut;

trait ToMut<'a> {
    type Output;
    fn to_mut(&'a mut self) -> Self::Output;
}

struct P<T>(T);

impl<'a, T: 'a> ToMut<'a> for P<T> {
    type Output = P<&'a mut T>;
    fn to_mut(&'a mut self) -> Self::Output {
        P(&mut self.0)
    }
}

fn init<'a, I, A, B>(t: &'a mut I)
where
    I: ToMut<'a, Output = P<A>>,
    A: DerefMut<Target = B>,
    B: Default,
{
    let mut f = t.to_mut();
    *f.0 = Default::default();
}

fn test() {
    let x = [99_usize];
    let mut p = P(&x[..]);
    init(&mut p);
    assert_eq!(p.0, &[0][..]);
}
2 Likes

I am struggling to understand why is that. ToMut implementation has Output which depends on input 'a, why generic bound can't express it?

Interesting, init_inner_action helper could be a standalone function, but then there is no way to write generic argument bound on fn init to say "generic argument I can be used with that function`, you use trait trick for that. I'll try to remember this technique, sounds useful.

At the end, I rewrote my program to avoid ToMut trait all together, manually doing what it is doing for my types, it became more verbose, but easier to understand.

The trait bound “for<'a> I: ToMut<'a, Output = P<A>>” literally says “I must implement ToMut<'a> for all lifetimes 'a, and for all lifetimes 'a the Output type of this implementation must be P<A>”, a direct consequence of this being that for all lifetimes 'a the Output type must be the same type, namely P<A>. It’s the Output = P<A> part that restricts the Output type so that it cannot depend on 'a.


What Rust does not offer is so-called higher-ranked types where you could write something like e.g.

fn init<I,A<'_>,B<'_>>(mut t: I) -> I
   where for<'a> I: ToMut<'a, Output = P<A<'a>>>,
     for<'a> A<'a>: DerefMut<Target = B<'a>>,
     for<'a> B<'a>: Default

introducing something like “type variables with lifetime arguments”, and thus allowing A and thus the Output type, (and perhaps, as above, also B) to depend on the lifetime used in the ToMut bound.

Generic associated types (GATs) allow some comparable using helper traits, but that would be hard to work with, usually requiring the caller to explicitly define what A<'a> (and B<'a>) are supposed to be; whereas the powereful / most user-friendly language would be one that can infer the higher-ranked parameters A/B. (As far as I remember, such inference is generally even intractable, but I’m aware of programming languages that can at least offer some limited support of this.)


What Rust does however allow is to just leave out the Output = part, and perhaps restrict for<'a> <I as ToMut<'a>>::Output: …trait bounds… in separate constraints in the where clause. It’s tedious to express “is a type of the form P<…something…> and …something…: …more trait bounds…” using current Rust features, unless you start introducing helper traits again.

1 Like

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.