Ok, let's handle this. First of all, a few tricks to help diagnose and understand the problem at hand:
-
I have re-ordered a bit the type definitions and impl
s so that they "chain" nicely;
-
I have replaced the &'lt self
shortand with the full syntax: self: &'lt Self
, replacing Self
with the actual type whenever possible. This is surprisingly not mentioned or suggested enough in the code and guides out there, and yet is one of the first things to do: why add the cognitive burden of replacing Self
with the type involved "by memory" when the type can just be explicitly spelled out?
-
When reordering, I have put the impl
before the trait definition, since I think it's hard to get a trait definition 100% correctly, whereas when impl
ementing for a specific type it is easier to come up with the right signature.
These things yield the following start:
#[derive(Clone)]
pub
struct Section<'data> {
pub
src: &'data [u8],
}
pub
struct R1<'data> {
sections: Vec<Section<'data>>,
}
impl<'data> R1<'data> {
pub
fn new (s: Section<'data>)
-> Self
{
R1 {
sections: vec![s],
}
}
}
impl<'data> /* Relocatable<'data> for */ R1<'data> {
fn sections (self: &'data R1<'data>)
-> BSI<'data>
// -> Box<dyn Iterator<Item = Section<'data>> + 'data>
{
Box::new(self.sections.iter().cloned())
}
}
By now, there is a "code smell" that is easier to spot, which @H2CO3 has already talked about: we are having a &'data Stuff<'data>
pattern. Let's try to loosen that a bit:
impl<'data> R1<'data> {
fn sections<'sections> (self: &'sections R1<'data>)
-> impl 'sections + Iterator<Item = Section<'data>>
{
self.sections
.iter() // impl 'sections + Iterator<Item = &'sections Section<'data>>
.cloned() // … Item = Section<'data>
}
}
So, as you can see, it is important not to unnecessarily tie the lifetime of the obtained / yielded items, with the lifetime used during the iteration itself. Remember that iterators are often used as:
// -------- 'iteration -------- |
let collection = iterable.iterate().collect(); // |
… // | 'items / 'collection
drop(collection) // |
So, as you can see, while it may be important to be able to keep access to the original big lifetime for the 'items
('data
in your example) so that the collected items can be kept around for as long as the original data can, it is also important to make sure we allow using a shorter lifetime for the iteration.
Now, let's trait
-ify the whole thing:
-
impl …
becomes Box<dyn …>
(with a wrapping Box::new(…)
in the function's body);
-
Since the return type of the impl
involve two lifetimes, so will that of the trait.
-
This, in turn, implies that the type alias will also involve two lifetimes.
type BSI<'iter, 'data> =
Box<dyn 'iter + Iterator<Item = Section<'data>>>
;
trait Relocatable<'data> {
fn sections<'sections> (self: &'sections Self)
-> BSI<'sections, 'data>
;
}
impl<'data> Relocatable<'data> for R1<'data> {
fn sections<'sections> (self: &'sections R1<'data>)
-> BSI<'sections, 'data>
{
Box::new(self.sections.iter().cloned())
}
}
This already suffices to make the code compile
I'll just add a final nit, regarding:
impl<'data> LinkState<'data> {
pub
fn rels<'l> (self: &'l LinkState<'data>)
-> impl Iterator<
Item = &'l Box<dyn Relocatable<'data> + 'data>,
>
{
self.relocatables.iter()
}
}
- Oh, by the way, I forgot I had also removed that
: 'data
bound on 'l
. Try to avoid such explicit bounds, since when they are used right they only add a bit of obvious stuff, but when misused, they hurt a lot. Remember, the :
"operator" on lifetimes must be viewed as ≥
. And when having stuff such as &'outer Thing<'inner>
, the natural bound is 'inner ≥ 'outer
, and thus 'inner : 'outer
. This is automagically deduced by Rust 95% of the time, let's say. So, when you go and add the incorrect 'outer ≥ 'inner
bound, such as with 'l : 'data
, the double ≥
inequality yields 'inner == 'outer
, and we are back at the &'same Thing<'same>
antipattern.
I hope you agree our yielded Item
s have too much indirection in them.
- On top of that, the signature is also a bit "lifetime heavy / redundant", so for aesthetic purposes, let's try to reduce it a bit. Generally, when
&'lifetime dyn Trait
is involved, it implicity expresses &'lifetime (dyn Trait + 'lifetime)
, and this is very often the right bound (the only / main exception I can think of is the Any
trait and its special relation with 'static
(see, for instance, the deprecated Error::cause()
vs. the correct Error::source()
), or object-safe Clone
-like methods.
This hints at Item = &'l dyn Relocatable<'data>
being a more idiomatic yielded object type, so let's use it:
impl<'data> LinkState<'data> {
pub
fn rels<'rels> (self: &'rels LinkState<'data>)
-> impl 'rels + Iterator<Item = &'rels dyn Relocatable<'data>>
{
self.relocatables
.iter()
.map(|ref_to_box: &'rels Box<_>| {
&**ref_to_box // flatten the indirection
})
}
}
-
Playground
-
Note how I've also used a more readable lifetime name, and how, in practice (c.f. your main()
, this 'rels
will be kind of the same as 'sections