Reputation: 4316
In this code (playground):
pub trait Value<'a>: PartialEq<Self> {
type Item;
fn items(&'a self) -> Option<Box<Iterator<Item = Self::Item> + 'a>>;
}
/// An example implementation for a scalar value
impl<'a> Value<'a> for String {
type Item = Self;
fn items(&self) -> Option<Box<Iterator<Item = Self>>> {
None
}
}
impl<'a, T> Value<'a> for Vec<T>
where T: PartialEq + 'a
{
type Item = &'a T;
fn items(&'a self) -> Option<Box<Iterator<Item = Self::Item> + 'a>> {
Some(Box::new(self.iter()))
}
}
fn main() {
fn assert_vec<'a, V>(l: V, r: V)
where V: Value<'a> + 'a
{
assert!(l != r);
assert!(l.items().is_some());
}
let l = vec!["one"];
let r = vec!["two"];
assert!(l.items().is_some());
assert!(l == l);
assert_vec(l, r);
}
The purpose of the trait is to (eventually) abstract over scalar values, arrays, associative arrays and nested data structures like rustc_serialize::json::Json
. That way, it should be possible to implement a generic algorithm for diffing arbitrary structures efficiently (see https://github.com/Byron/treediff-rs).
Step by step I reached a point where the trait appears usable (see lines 31...), but I am unable to declare a function that can generically use the traits items()
method (see lines 24...37). The lifetime declarations seem to be broken and prohibit the trait to be used as intended.
How can the code be adjusted to compile?
Upvotes: 1
Views: 100
Reputation: 65692
Here's a version of your code that "works":
pub trait Value: PartialEq<Self> {
type Item;
fn items<'a>(&'a self) -> Option<Box<Iterator<Item = &'a Self::Item> + 'a>>;
}
/// An example implementation for a scalar value
impl Value for String {
type Item = Self;
fn items(&self) -> Option<Box<Iterator<Item = &Self>>> {
None
}
}
impl<T> Value for Vec<T>
where T: PartialEq
{
type Item = T;
fn items<'a>(&'a self) -> Option<Box<Iterator<Item = &'a Self::Item> + 'a>> {
Some(Box::new(self.iter()))
}
}
fn main() {
fn assert_vec<V>(l: V, r: V)
where V: Value
{
assert!(l != r);
assert!(l.items().is_some());
}
let l = vec!["one"];
let r = vec!["two"];
assert!(l.items().is_some());
assert!(l == l);
assert_vec(l, r);
}
What I changed is that I moved the lifetime parameter from the trait
to the items
method, and then I changed the return type of items
so that the iterator must emit references, so I could attach the correct lifetime.
What was wrong with assert_vec
? Consider what the type of the subexpression l
should be in the expression l.items()
. Based on your definition of the Value
trait, that expression had to be of type &'a Vec<String>
, where 'a
here refers to the 'a
lifetime parameter on assert_vec
. However, that expression cannot possibly be of that type, because lifetime parameters always refer to lifetimes that extend beyond the function call, yet l
is a local variable, so its lifetime ends when the function returns (therefore it has a shorter lifetime, hence "l
does not live long enough"). By moving the lifetime on the method instead, the compiler can infer the proper lifetime (the lifetime of the local variable).
Note that my working version of the code forces implementations of Value
to return iterators that emit references. Currently, in Rust, there's no way to express if the type is a reference, set its lifetime to X, but if it's not, ignore the lifetime parameter. What we need is associated type constructors or higher-kinded typed, but unfortunately neither is implemented yet.
Upvotes: 2