Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify lifetime bounds for a closure involving references to intermediate local variables?

Tags:

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
}

I want it to work on types where T is non-Copy. 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
 --> src/main.rs:6:5
  |
6 |     &<&'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
 --> src/main.rs:8:13
  |
8 |     let t = x + x;
  |             ^
  |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the function body at 3:1...
 --> src/main.rs:3:1
  |
3 | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
4 | | where
5 | |     &'a T: Add,
6 | |     &'b <&'a T as Add>::Output: Mul,
... |
9 | |     &t * &t
10| | }
  | |_^
note: ...so that expression is assignable (expected &T, found &'a T)
 --> src/main.rs:8:13
  |
8 |     let t = x + x;
  |             ^
note: but, the lifetime must be valid for the lifetime 'b as defined on the function body at 3:1...
 --> src/main.rs:3:1
  |
3 | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
4 | | where
5 | |     &'a T: Add,
6 | |     &'b <&'a T as Add>::Output: Mul,
... |
9 | |     &t * &t
10| | }
  | |_^
note: ...so that the type `<&T as std::ops::Add<&'a T>>::Output` is not borrowed for too long
 --> src/main.rs:9:10
  |
9 |     &t * &t
  |          ^^

error[E0490]: a value of type `<&T as std::ops::Add<&'a T>>::Output` is borrowed for too long
 --> src/main.rs:9:10
  |
9 |     &t * &t
  |          ^^
  |
note: the type is valid for the lifetime 'b as defined on the function body at 3:1
 --> src/main.rs:3:1
  |
3 | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
4 | | where
5 | |     &'a T: Add,
6 | |     &'b <&'a T as Add>::Output: Mul,
... |
9 | |     &t * &t
10| | }
  | |_^
note: but the borrow lasts for the lifetime 'a as defined on the function body at 3:1
 --> src/main.rs:3:1
  |
3 | / fn double_and_square<'a, 'b, T>(x: &'a T) -> <&'b <&'a T as Add>::Output as Mul>::Output
4 | | where
5 | |     &'a T: Add,
6 | |     &'b <&'a T as Add>::Output: Mul,
... |
9 | |     &t * &t
10| | }
  | |_^

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".

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?

like image 505
jbatez Avatar asked Mar 17 '18 20:03

jbatez


1 Answers

Buckle up...

use std::ops::{Add, Mul};

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

Easy enough, right? ;-)

Let's take it step by step...

  1. You wish to take in a reference to a type, but the reference needs to implement Add. where clauses let you write complex types on either side of the :, so we use &'a T: Add.

  2. This will return some value that we take another reference to. However, the caller of double_and_square cannot specify the lifetime, since it only exists inside the function. This means we need to use a higher-ranked trait bound: for <'b>.

  3. We have to use the type of the output of the Add operation, say that it implements Mul, and the output type is the generic R.


I'd recommend not taking references in the original function as it's way easier to understand:

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

&Foo is a separate type from Foo and can be passed as the concrete type of T, so this should be able to be used in any place the original was, and probably usable in even more cases.

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

Immutable references to types are always Copy, even if the type itself doesn't implement Copy. Thus, you can call this function with e.g. T = i32 or a T = &NonCopy. The original case that only accepted references would only accept the second one.

In an ideal world, you'd be able to avoid the generic type R and just say <...something...>::Output, but as far as I know that's not currently possible.

like image 163
Shepmaster Avatar answered Sep 23 '22 12:09

Shepmaster