I have a Fibonacci
struct that can be used as an iterator for anything that implements One
, Zero
, Add
and Clone
. This works great for all integer types.
I want to use this struct for BigInteger
types which are implemented with a Vec
and are expensive to call clone()
on. I would like to use Add
on two references to T
which then returns a new T
(no cloning then).
For the life of me I can't make one that compiles though...
Working:
extern crate num;
use std::ops::Add;
use std::mem;
use num::traits::{One, Zero};
pub struct Fibonacci<T> {
curr: T,
next: T,
}
pub fn new<T: One + Zero>() -> Fibonacci<T> {
Fibonacci {
curr: T::zero(),
next: T::one(),
}
}
impl<'a, T: Clone + Add<T, Output = T>> Iterator for Fibonacci<T> {
type Item = T;
fn next(&mut self) -> Option<T> {
mem::swap(&mut self.next, &mut self.curr);
self.next = self.next.clone() + self.curr.clone();
Some(self.curr.clone())
}
}
#[test]
fn test_fibonacci() {
let first_12 = new::<i64>().take(12).collect::<Vec<_>>();
assert_eq!(vec![1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144], first_12);
}
Desired:
extern crate num;
use std::ops::Add;
use std::mem;
use num::traits::{One, Zero};
pub struct Fibonacci<T> {
curr: T,
next: T,
}
pub fn new<T: One + Zero>() -> Fibonacci<T> {
Fibonacci {
curr: T::zero(),
next: T::one(),
}
}
impl<'a, T: Clone + 'a> Iterator for Fibonacci<T>
where
&'a T: Add<&'a T, 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())
}
}
#[test]
fn test_fibonacci() {
let first_12 = new::<i64>().take(12).collect::<Vec<_>>();
assert_eq!(vec![1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144], first_12);
}
This gives the error
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
--> src/main.rs:27:21
|
27 | self.next = &self.next + &self.curr;
| ^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 25:5...
--> src/main.rs:25:5
|
25 | / fn next(&mut self) -> Option<T> {
26 | | mem::swap(&mut self.next, &mut self.curr);
27 | | self.next = &self.next + &self.curr;
28 | | Some(self.curr.clone())
29 | | }
| |_____^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:27:21
|
27 | self.next = &self.next + &self.curr;
| ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 19:1...
--> src/main.rs:19:1
|
19 | / impl<'a, T: Clone + 'a> Iterator for Fibonacci<T>
20 | | where
21 | | &'a T: Add<&'a T, Output = T>,
22 | | {
... |
29 | | }
30 | | }
| |_^
note: ...so that types are compatible (expected std::ops::Add, found std::ops::Add<&'a T>)
--> src/main.rs:27:32
|
27 | self.next = &self.next + &self.curr;
| ^
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
--> src/main.rs:27:34
|
27 | self.next = &self.next + &self.curr;
| ^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 25:5...
--> src/main.rs:25:5
|
25 | / fn next(&mut self) -> Option<T> {
26 | | mem::swap(&mut self.next, &mut self.curr);
27 | | self.next = &self.next + &self.curr;
28 | | Some(self.curr.clone())
29 | | }
| |_____^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:27:34
|
27 | self.next = &self.next + &self.curr;
| ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 19:1...
--> src/main.rs:19:1
|
19 | / impl<'a, T: Clone + 'a> Iterator for Fibonacci<T>
20 | | where
21 | | &'a T: Add<&'a T, Output = T>,
22 | | {
... |
29 | | }
30 | | }
| |_^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:27:34
|
27 | self.next = &self.next + &self.curr;
| ^^^^^^^^^^
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With