8

I'm trying to implement a generic Cons List, one somewhat more advanced than the one used in chapter 15 of the book:

use std::fmt::Debug;

#[derive(Debug)]
enum List<T> {
    Nil,
    Cons(T, Box<List<T>>),
}

impl<T> List<T>
where
    T: Debug,
{
    fn from_iterable(iterator: &Iterator<Item = T>) -> Self {
        iterator.fold(List::Nil, |acc, value| List::Cons(value, Box::new(acc)))
    }
}

fn main() {
    println!("{:?}", List::from_iterable(&(1..10)));
}

(playground)

My code does not compile and it has a really confusing message:

error: the `fold` method cannot be invoked on a trait object
  --> src/main.rs:14:18
   |
14 |         iterator.fold(List::Nil, |acc, value| List::Cons(value, Box::new(acc)))
   |                  ^^^^

What does this message mean?

I have seen this somehow related question, but even if this one is a duplicate my current knowledge is too limited to connect the dots.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Yury Tarabanko
  • 44,270
  • 9
  • 84
  • 98
  • 1
    Why did you decide to use a trait object in the first place? – Shepmaster Feb 16 '18 at 18:53
  • @Shepmaster I thought it was good idea to have a factory method that could create a list from any iterator because I only need argument to be foldable. Are there any reasons not to make my code that generic? – Yury Tarabanko Feb 16 '18 at 18:56

1 Answers1

7

You have a larger problem. You have accepted a reference to a trait object that is immutable. This means that you cannot call Iterator::next, the most primitive operation on an iterator:

impl<T> List<T>
where
    T: Debug,
{
    fn from_iterable(iterator: &dyn Iterator<Item = T>) -> Self {
        iterator.next();
        panic!();
    }
}
error[E0596]: cannot borrow `*iterator` as mutable, as it is behind a `&` reference
  --> src/main.rs:16:9
   |
15 |     fn from_iterable(iterator: &dyn Iterator<Item = T>) -> Self {
   |                                ----------------------- help: consider changing this to be a mutable reference: `&mut dyn std::iter::Iterator<Item = T>`
16 |         iterator.next();
   |         ^^^^^^^^ `iterator` is a `&` reference, so the data it refers to cannot be borrowed as mutable

If you follow this error suggestion and update the call site to pass in a mutable reference, your code works:

impl<T> List<T>
where
    T: Debug,
{
    fn from_iterable(iterator: &mut dyn Iterator<Item = T>) -> Self {
        iterator.fold(List::Nil, |acc, value| List::Cons(value, Box::new(acc)))
    }
}

fn main() {
    println!("{:?}", List::from_iterable(&mut (1..10)));
}

However, it's not common to use trait objects for this type of problem, as they involve dynamic dispatch and some (small) amount of runtime overhead. Instead, it's far more common to use static dispatch with generics:

impl<T> List<T>
where
    T: Debug,
{
    fn from_iterable(iterator: impl IntoIterator<Item = T>) -> Self {
        iterator
            .into_iter()
            .fold(List::Nil, |acc, value| List::Cons(value, Box::new(acc)))
    }
}

fn main() {
    println!("{:?}", List::from_iterable(1..10));
}

I also switched to IntoIterator as it's a bit more ergonomic for callers.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you. BTW how do you manage to get "use `&mut Iterator` here to make mutable" message out of your compiler? This one is way more clear because I've totally missed the point that fold actually mutates iterator. – Yury Tarabanko Feb 16 '18 at 19:07
  • 1
    @YuryTarabanko well, the original form of your code *doesn't* have that error message, that's why it's tougher to figure out. I encourage you [to file an issue on the Rust repository](https://github.com/rust-lang/rust/) with your original case, the error message and how it could be improved; the Rust team is always looking to improve documentation and errors. Otherwise, I just took the code and compiled it in the playground / with `cargo run`. – Shepmaster Feb 16 '18 at 19:11
  • 1
    I see. Someone has already filed one. [Opened issue](https://github.com/rust-lang/rust/issues/37914) – Yury Tarabanko Feb 16 '18 at 19:16
  • 1
    @turbulencetoo yes, there's a blanket `impl<'a, I> Iterator for &'a mut I where I: Iterator + ?Sized`. Move semantics apply equally to value types (`T`) and references (`&T` / `&mut T`). – Shepmaster Feb 16 '18 at 19:42
  • Thanks. Bjorn3, in the issue linked above by Yuri also helped: "If you have a non mutable reference rustc will deref the reference to get something it can call [method] on. However if you have a mutable reference rustc knows that there is a implementation of Iterator for it without having to dereference it" – turbulencetoo Feb 16 '18 at 19:54