How to write a trait bound for adding two references of a generic type?

How to write a trait bound for adding two references of a generic type?

Let’s start with a simplified example:

fn add_things<T>(a: &T, b: &T) {
    a + b;
}

This has the error

error[E0369]: binary operation `+` cannot be applied to type `&T`
 --> src/lib.rs:2:5
  |
2 |     a + b;
  |     ^^^^^
  |
  = note: an implementation of `std::ops::Add` might be missing for `&T`

As the compiler hints, we need to guarantee that Add is implemented for &T. We can express that directly by adding an explicit lifetime to our types and also using that in our trait bounds:

use std::ops::Add;

fn add_things<'a, T>(a: &'a T, b: &'a T)
where
    &'a T: Add,
{
    a + b;
}

Next, let’s try a slightly different approach — instead of being handed a reference, we will create one inside the function:

fn add_things<T>(a: T, b: T) {
    let a_ref = &a;
    let b_ref = &b;

    a_ref + b_ref;
}

We get the same error:

error[E0369]: binary operation `+` cannot be applied to type `&T`
 --> src/lib.rs:5:5
  |
5 |     a_ref + b_ref;
  |     ^^^^^^^^^^^^^
  |
  = note: an implementation of `std::ops::Add` might be missing for `&T`

However, trying to add the same fix as before doesn’t work. It’s also a bit awkward because the lifetime isn’t associated with any of the arguments passed in:

use std::ops::Add;

fn add_things<'a, T: 'a>(a: T, b: T)
where
    &'a T: Add,
{
    let a_ref = &a;
    let b_ref = &b;

    a_ref + b_ref;
}
error[E0597]: `a` does not live long enough
  --> src/lib.rs:7:17
   |
3  | fn add_things<'a, T: 'a>(a: T, b: T)
   |               -- lifetime `'a` defined here
...
7  |     let a_ref = &a;
   |                 ^^
   |                 |
   |                 borrowed value does not live long enough
   |                 assignment requires that `a` is borrowed for `'a`
...
11 | }
   | - `a` dropped here while still borrowed

Placing the 'a lifetime on the impl means that the caller of the method gets to determine what the lifetime should be. Since the reference is taken inside the method, the caller can never even see what that lifetime would be.

Instead, you want to place a restriction that a reference of an arbitrary lifetime implements a trait. This is called a Higher Ranked Trait Bound (HRTB):

use std::ops::Add;

fn add_things<T>(a: T, b: T)
where
    for<'a> &'a T: Add,
{
    let a_ref = &a;
    let b_ref = &b;

    a_ref + b_ref;
}

Applied back to your original code, you were very close:

impl<T> Iterator for Fibonacci<T>
where
    T: Clone,
    for<'a> &'a T: Add<Output = T>,
{
    type Item = T;

    fn next(&mut self) -> Option<T> {
        mem::swap(&mut self.next, &mut self.curr);
        self.next = &self.next + &self.curr;
        Some(self.curr.clone())
    }
}

See also:

Leave a Comment