Hello,
I am trying to have functions return an iterator. However, this iterator is made by other functions (actually, same function but implemented on other types). And in the ideal, its actually several chained iterators (either local or from these other types).
The problem I face is that compiler errors out on the lifetime (too short) of the values used when calling the function returning iterator for other types. Despite these values not really being borrowed anymore once these functions have returned (ie. iterators they make are independent of the value initially used to create them).
The context is a lib implementing a serialisation codec for a protocol.
This particular function is for the encoding path. It is defined as a trait implemented on many types (mostly primitives).
The way this serialisation protocol works is that most type encoding functions ends up calling the encoding functions of a few "fundamental" types (eg. bool and u32 stand by themselves, while other types like i32 or f32 encoding ends up "composing" from bool and u32, plus some parts of themselves ; there are more complex cases than that).
It is written with no_std in mind as it aims at embedded targets. Even though it will have an allocator, heap allocations should be kept to a minimum, and especially not in that particular hot loop serialising complex messages.
Also, message structures are not known at compile time. That rules out pre-generation/macro unfortunately. Hence, the current solution revolves around this system of chainable iterators (all yielding the same type though, akin to bytes).
Here I have the problem reduced to its minimal form (obviously, the iterators do real stuff instead of just returning None, but I found out their content is not actually related to the problem): Rust Playground
struct MyIterator();
impl Iterator for MyIterator {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
None
}
}
trait Streamable {
fn to_stream_iter(&self) -> impl Iterator<Item = u8>;
}
impl Streamable for u32 {
fn to_stream_iter(&self) -> impl Iterator<Item = u8> {
MyIterator()
// Does not work neither:
// std::iter::from_fn(|| None)
}
}
impl Streamable for i32 {
fn to_stream_iter(&self) -> impl Iterator<Item = u8> {
let next_value = 0u32;
next_value.to_stream_iter()
// The followings do not work neither:
// next_value.to_stream_iter().into_iter().map(|a| a)
// next_value.borrow().clone().to_stream_iter()
// next_value.to_stream_iter().chain(std::iter::from_fn(|| None))
// (move || next_value.to_stream_iter())()
}
}
fn main() {
let data: Vec<u8> = 0i32.to_stream_iter().collect();
dbg!(data);
}
With compiler output:
Compiling playground v0.0.1 (/playground)
error[E0597]: `next_value` does not live long enough
--> src/main.rs:26:9
|
24 | fn to_stream_iter(&self) -> impl Iterator<Item = u8> {
| - let's call the lifetime of this reference `'1`
25 | let next_value = 0u32;
| ---------- binding `next_value` declared here
26 | next_value.to_stream_iter()
| ^^^^^^^^^^-----------------
| |
| borrowed value does not live long enough
| argument requires that `next_value` is borrowed for `'1`
...
33 | }
| - `next_value` dropped here while still borrowed
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` (bin "playground") due to 1 previous error
As you can see commented out, I tried several forms, to no avail.
It seems to consider that next_value
remains borrowed after the call to to_stream_iter()
, and until I actually consume the iterator. Notably, if I do:
let mut next_value = 0u32;
let iter = next_value.to_stream_iter();
next_value = 1u32;
iter
It complains it cannot change next_value
at it remains borrowed. But if I do:
let mut next_value = 0u32;
let iter = next_value.to_stream_iter();
let data: Vec<u8> = iter.collect();
next_value = 1u32;
dbg!(data);
Then it is ok with it.
But I cannot understand why it remains borrowed despite not being used?!
However, I did find two cases in which it works (but neither is usable):
- If I replace
next_value
with a direct0u32
, ie. skipping the let. Then it works, but it's useless to me since this value is always derived from something else, and not hard coded. - If I replace
impl Iterator<Item = u8>
withMyIterator
in the return type of these functions. Like in this playground: Rust Playground
But this is also not really useful because then I can no longer use chain...
I am now trying to add some sort of chain functionality to my iterator. But I am already hitting issue with recursive definition of the struct...
Hence why sticking to impl Iterator<Item = u8>
seems more approriate.
I am also even more puzzled as to why it does not work with the impl
due to an unused borrow, but with a concrete type it does not find this unused borrow?! Is there a way to specify lifetime that would make the compiler understand this is fine?