I have a trait method that finds a reference to an element in a collection by linearly scanning through its elements.
I'd like to be able to implement this once for both Vec<Tag>
and &'a [Tag]
(and ideally support other iterable data structures too).
In the code below, the instances of TagFinder
are identically implemented for Vec<Tag>
and &'a [Tag]
, but I can't find a way to express this generically. Is it possible?
This other question seems relevant, but I have an extra level of indirection here in that I'm dealing with "iterables" and not iterators.
Relatedly, it seems it would be handy if there were a trait like IntoIterator
that exposed an iterator of references (i.e. Vec<T>
and &[T]
would both iterate over &T
, rather than Vec<T>
exposing an owning iterator). I'm not sure why such a thing doesn't exist.
struct Tag {
key: String,
value: String,
}
trait TagFinder {
fn find_tag(&self, key: &str) -> Option<&str>;
}
impl<'a> TagFinder for &'a [Tag] {
fn find_tag(&self, key: &str) -> Option<&str> {
find_tag(self.into_iter(), key)
}
}
impl TagFinder for Vec<Tag> {
fn find_tag(&self, key: &str) -> Option<&str> {
find_tag(self.into_iter(), key)
}
}
fn find_tag<'a, I>(tags: I, key: &str) -> Option<&'a str>
where
I: Iterator<Item = &'a Tag>,
{
tags.filter_map(|tag| match tag {
&Tag {
key: ref k,
value: ref v,
} if k == key =>
{
Some(v as &str)
}
_ => None,
}).next()
}
fn main() {
let v = vec![
Tag {
key: "a".to_owned(),
value: "1".to_owned(),
},
Tag {
key: "b".to_owned(),
value: "2".to_owned(),
},
];
let s: &[Tag] = &v;
assert!(v.find_tag("b") == Some("2"));
assert!(s.find_tag("b") == Some("2"));
}
Edit
After some playing around I've come up with the following. It works, but I'm not really comfortable with why it works.
The trait now consumes
self
, which would not be at all desirable, except for the fact that the only implementers ofIntoIterator<Item = &'a Tag>
seem to be borrowing types, so theself
that is destroyed is only a reference. I'm a bit wary because there is nothing (except convention) stopping someone implementing that for an owning type likeVec
.Moving the lifetime parameter from the method (elided) to the trait is weird. I'm finding it hard to understand how the return value ends up with a sensible lifetime.
Why does
v.find_tag(...)
work? The receiver here is aVec
not a reference. How is Rust converting it to a reference?
Thanks. :)
trait TagFinder<'a> {
fn find_tag(self, key: &str) -> Option<&'a str>;
}
impl<'a, T> TagFinder<'a> for T
where
T: IntoIterator<Item = &'a Tag>,
{
fn find_tag(self, key: &str) -> Option<&'a str> {
find_tag(self.into_iter(), key)
}
}