How to specify generic function bounds involving the lifetimes of intermediate variables


#1

I’m trying to write a function like the following in Rust:

fn double_and_square<'a, T>(x: &'a T) -> /*whatever the output type of `&t * &t` is*/ {
    let t = x + x;
    &t * &t
}

And I want it to work on types where T is non-Copy.

Basically, I need to specify not only that &'a T implements Add (easy), but also that a reference to its output type with the lifetime of local variable t implements Mul.

Attempt #1 (no lifetime specified for the intermediate type):

fn double_and_square<'a, T>(x: &'a T) -> <&<&'a T as Add>::Output as Mul>::Output
where &'a T: Add, &<&'a T as Add>::Output: Mul {
    let t = x + x;
    &t * &t
}

Results in the following compiler error:

error[E0106]: missing lifetime specifier
    |
    | where &'a T: Add, &<&'a T as Add>::Output: Mul {
    |                   ^ expected lifetime parameter

Attempt #2 (alright, I’ll add a lifetime specifier):

fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
where &'a T: Add, &'b <&'a T as Add>::Output: Mul {
    let t = x + x;
    &t * &t
}

Results in the following compiler error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
    |
    |     let t = x + x;
    |             ^
    |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the function body
    |
    | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
    | | where &'a T: Add, &'b <&'a T as Add>::Output: Mul {
    | |     let t = x + x;
    | |     &t * &t
    | | }
    | |_^
note: ...so that expression is assignable (expected &T, found &'a T)
    |
    |     let t = x + x;
    |             ^
note: but, the lifetime must be valid for the lifetime 'b as defined on the function body
    |
    | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
    | | where &'a T: Add, &'b <&'a T as Add>::Output: Mul {
    | |     let t = x + x;
    | |     &t * &t
    | | }
    | |_^
note: ...so that the type `<&T as std::ops::Add<&'a T>>::Output` is not borrowed for too long
    |
    |     &t * &t
    |          ^^

error[E0490]: a value of type `<&T as std::ops::Add<&'a T>>::Output` is borrowed for too long
    |
    |     &t * &t
    |          ^^
    |
note: the type is valid for the lifetime 'b as defined on the function body
    |
    | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
    | | where &'a T: Add, &'b <&'a T as Add>::Output: Mul {
    | |     let t = x + x;
    | |     &t * &t
    | | }
    | |_^
note: but the borrow lasts for the lifetime 'a as defined on the function body
    |
    | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
    | | where &'a T: Add, &'b <&'a T as Add>::Output: Mul {
    | |     let t = x + x;
    | |     &t * &t
    | | }
    | |_^

The way that I read the lifetime must be valid for the lifetime 'b as defined on the function body tells me the compiler thinks 'b is supposed to live as long as or longer than the whole function body, whereas I just want it to mean “any lifetime”.

Question:

Is what I’m trying to do even possible in Rust? If not, are there any proposed changes I should watch that will make it possible?


#2

I would write this as:

fn double_and_square<'a, T, R>(x: &'a T) -> R
where
    &'a T: Add,
    for<'t> &'t <&'a T as Add>::Output: Mul<Output = R>,
{
    let t = x + x;
    &t * &t
}

#3

Another way to write @dtolnay’s version, which I find a bit easier on the eyes, is:

fn double_and_square<'a, T, O, R>(x: &'a T) -> R
where
    &'a T: Add<Output = O>,
    for<'t> &'t O: Mul<Output = R>,
{
    let t = x + x;
    &t * &t
}

#4

Ah, I didn’t know about that for<'t> syntax (higher-rank trait bounds). It’s exactly what I was looking for; thanks!

I also didn’t know you could specify bounds on trait member type aliases in the where clause like <Output = R> above. That simplifies my code in other places too.