In implementing a version of a LazyList (an immutable lazily-computed memoized singly-linked list, much as Haskell lists), I have run into a problem of implementing IntoIterator
in that the code does not drop the reference when I think it should. The following code has been simplified so as just to show the problem; thus, is not generic and does not include all of the methods not related to implementing IntoIterator
:
use std::cell::UnsafeCell;
use std::mem::replace;
use std::rc::Rc;
// only necessary because Box<FnOnce() -> R> doesn't yet work...
trait Invoke<R = ()> {
fn invoke(self: Box<Self>) -> R;
}
impl<'a, R, F: 'a + FnOnce() -> R> Invoke<R> for F {
#[inline(always)]
fn invoke(self: Box<F>) -> R {
(*self)()
}
}
// not thread safe
struct Lazy<'a, T: 'a>(UnsafeCell<LazyState<'a, T>>);
enum LazyState<'a, T: 'a> {
Unevaluated(Box<Invoke<T> + 'a>),
EvaluationInProgress,
Evaluated(T),
}
use self::LazyState::*;
impl<'a, T: 'a> Lazy<'a, T> {
#[inline]
fn new<F: 'a + FnOnce() -> T>(func: F) -> Lazy<'a, T> {
Lazy(UnsafeCell::new(Unevaluated(Box::new(func))))
}
#[inline]
pub fn evaluated(val: T) -> Lazy<'a, T> {
Lazy(UnsafeCell::new(Evaluated(val)))
}
#[inline]
fn value(&'a self) -> &'a T {
unsafe {
match *self.0.get() {
Evaluated(_) => (), // nothing required; already Evaluated
EvaluationInProgress => panic!("Lazy::force called recursively!!!"),
_ => {
let ue = replace(&mut *self.0.get(), EvaluationInProgress);
if let Unevaluated(thnk) = ue {
*self.0.get() = Evaluated(thnk.invoke());
} // no other possiblity!
}
} // following just gets evaluated, no other state possible
if let Evaluated(ref v) = *self.0.get() {
return v;
} else {
unreachable!();
}
}
}
}
enum LazyList<'a> {
Empty,
Cons(i32, RcLazyListNode<'a>),
}
type RcLazyListNode<'a> = Rc<Lazy<'a, LazyList<'a>>>;
impl<'a> LazyList<'a> {
fn iter(&self) -> Iter<'a> {
Iter(self)
}
}
struct Iter<'a>(*const LazyList<'a>);
impl<'a> Iterator for Iter<'a> {
type Item = &'a i32;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if let LazyList::Cons(ref v, ref r) = *self.0 {
self.0 = r.value();
Some(v)
} else {
None
}
}
}
}
impl<'a> IntoIterator for &'a LazyList<'a> {
type Item = &'a i32;
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
fn main() {
let test2 = LazyList::Cons(2, Rc::new(Lazy::evaluated(LazyList::Empty)));
let test = LazyList::Cons(1, Rc::new(Lazy::new(move || test2)));
// let itr = Iter(&test); // works
// let itr = (&test).iter(); // works
let itr = IntoIterator::into_iter(&test); // not working
for v in itr {
println!("{}", v);
}
}
The above code fails with:
rustc 1.13.0 (2c6933acc 2016-11-07)
error: `test` does not live long enough
--> <anon>:103:40
|
103 | let itr = IntoIterator::into_iter(&test); // not working
| ^^^^ does not live long enough
...
107 | }
| - borrowed value dropped before borrower
|
= note: values in a scope are dropped in the opposite order they are created
As noted in the comments in main()
, the code is usable except when called as a reference through the IntoIterator trait. This may be a bug in implementing traits for references where the ownership of the returned iterator containing a pointer is not transferred to the same scope as the call to IntoIterator::into_iter
but rather to the 'static
lifetime, thus, it is not dropped when expected.
How do I implement this, if possible? I've tried adding a std::marker::PhantomData<>
marker field to the Iter
struct but it seems that, too, is assigned a 'static
lifetime.
When you implemented IntoIterator
, you unified the lifetimes between the reference to the list and the items that the list contains:
impl<'a> IntoIterator for &'a LazyList<'a>
That requires that 'a
must be the shorter of the lifetimes. That's not useful in this case. Instead, you need to have two distinct lifetimes:
impl<'l, 'i> IntoIterator for &'l LazyList<'i> {
type Item = &'i i32;
type IntoIter = Iter<'i>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}