2

I implemented a BoxedIterator in Rust that just boxes another Iterator as a trait object. The full implementation is on Github. Why does Rust compile this code without complaint but fail with an "out of memory" message (OOM) when it first tries to call next on the Iterator trait object in the Box?

As far as I can tell it doesn't allocate much memory before failing, so I'm inclined to think the OOM message is not correct.

//! BoxedIterator just wraps around a box of an iterator, it is an owned trait object.
//! This allows it to be used inside other data-structures, such as a `Result`.
//! That means that you can `.collect()` on an `I where I: Iterator<Result<V, E>>` and get out a
//! `Result<BoxedIterator<V>, E>`. And then you can `try!` it. At least, that was my use-case.

use std::iter::FromIterator;
use std::iter::IntoIterator;

pub struct BoxedIterator<T> {
    iter: Box<Iterator<Item = T>>,
}

impl<T> Iterator for BoxedIterator<T> {
    type Item = T;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next() // The OOM comes from this call of `next`
    }
}

impl<T> FromIterator<T> for BoxedIterator<T> {
    fn from_iter<I>(iter: I) -> Self
        where I: IntoIterator<Item = T>,
              I::IntoIter: 'static
    {
        BoxedIterator { iter: Box::new(iter.into_iter()) }
    }
}

use std::fs::File;
use std::io;

fn main() {
    let iter: Result<BoxedIterator<File>, io::Error> =
        vec!["/usr/bin/vi"].iter().cloned().map(File::open).collect();
    let mut iter = iter.unwrap();

    println!("{:?}", iter.next());
}

I don't think I'm going to use this code, as I've figured that my use case will need to traverse the Iterator of Results completely to extract any errors so I might as well gather them in a Vec at that point. But I'm still curious about this OOM.

While creating a minimal example, I found that without doing the File IO, I get a segfault:

use iterator::BoxedIterator;

fn main() {
    let iter: Result<BoxedIterator<&str>, ()> = 
        vec![Ok("test1"), Ok("test2")].iter().cloned().collect();
    let mut iter = iter.unwrap();

    println!("{:?}", iter.next());
}

If I don't use any Result, just create a BoxedIterator with collect, the code works as expected:

use iterator::BoxedIterator;

fn main() {
    let mut iter: BoxedIterator<&str> = vec!["test1", "test2"].iter().cloned().collect();

    println!("{:?}", iter.next());
    // prints: Some("test1")
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Apanatshka
  • 5,958
  • 27
  • 38
  • 1
    The OOM only happens in debug mode. In release mode the program runs fine https://play.rust-lang.org/?gist=5ada00482267db3ae94468b47ec0a014&version=stable&backtrace=0 . I think you should report this as a bug. – malbarbo Jul 07 '16 at 21:07
  • 1
    I've taken the liberty of reporting a bug for you (https://github.com/rust-lang/rust/issues/34714). – Steven Jul 07 '16 at 23:42
  • Can you [edit] your question to include the text that indicates that this is an out-of-memory error? When I run it, I "just" get a segfault. – Shepmaster Jul 08 '16 at 01:02

1 Answers1

4

Your implementation of FromIterator isn't correct; specifically, you aren't allowed to put an I::IntoIter: 'static bound in that position. The bounds on your implementation have to match the bounds on the trait itself. The compiler should diagnose this, but currently doesn't.

At a higher level, I'm not sure what you're trying to do. Where do you expect the File handles to be stored? You would normally write something like this:

let files: Result<Vec<File>, io::Error> =
    ["/bin/bash"].iter().cloned().map(File::open).collect();
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Eli Friedman
  • 2,343
  • 1
  • 13
  • 11
  • For the higher level question: see the note below the first piece of code (originally prefixed by N.B.) which is basically commenting on the uselessness of the code ^^ I indeed switched to collecting the `File` handles into a `Vec`. – Apanatshka Jul 08 '16 at 15:10