Search code examples
genericsrustheap-memorygenerator

Rust expects two levels of boxing for generator while I only specified one


I am encountering a compiler error for something that I feel should work.

I tried this code (note generators are nightly-only at the time of writing):

#![feature(generators, generator_trait)]

use std::ops::Generator;

struct Value {}

struct Container<G: Generator<Yield = Value, Return = ()>> {
    generator: Box<G>,
}

impl Container<Box<Generator<Yield = Value, Return = ()>>> {
    pub fn new(&mut self) -> Box<Self> {
        let generator: Box<Generator<Yield = Value, Return = ()>> = Box::new(|| loop {
            yield Value {}
        });
        Box::new(Container {
            generator: generator,
        })
    }
}

fn main() {}

where I get this error:

error[E0308]: mismatched types
  --> src/main.rs:20:24
   |
20 |             generator: generator,
   |                        ^^^^^^^^^ expected struct `std::boxed::Box`, found trait std::ops::Generator
   |
   = note: expected type `std::boxed::Box<std::boxed::Box<std::ops::Generator<Yield=Value, Return=()>>>`
              found type `std::boxed::Box<std::ops::Generator<Yield=Value, Return=()>>`

error: aborting due to previous error

I don't understand why two levels of boxing are expected here, I only asked for one (Box<G>).

It looks like Generator is indeed a trait, not an alias for Box<...>. I can't think of other explanations.

I can easily resolve the error by replacing Box<G> by G, but I want to know why my way does not work (could it be a bug?).

Nightly version 1.28.0-nightly (2018-06-15 967c1f3be1c9ce0469ae) in debug mode on the playground (but I have a similar issue locally with more complex code).


Solution

  • There are a couple of problems here.

    First, you define Containter<G> as having a member of type Box<G>. Then you write the impl for Containter<Box<G>>, that naturally has a member of type Box<Box<G>>. Probably you just want:

    impl Container<Generator<Yield = Value, Return = ()>> {
        ...
    }
    

    Second, if you compile again you have this error:

    |

    16 |         Box::new(Container {
       |                  ^^^^^^^^^ `std::ops::Generator<Yield=Value, Return=()>` does not have a constant size known at compile-time
       |
       = help: the trait `std::marker::Sized` is not implemented for `std::ops::Generator<Yield=Value, Return=()>`
    note: required by `Container`
      --> a.rs:7:1
       |
    7  | struct Container<G: Generator<Yield = Value, Return = ()>> {
       | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    This means that struct Containter requires that G is sized, but your generator doesn't implement it. That's true, you want Box<G> to be a trait object, so G will be unsized (a trait type). But type arguments in structs are Sized by default. The solution is to add ?Sized requirement to Container:

    struct Container<G: Generator<Yield = Value, Return = ()> + ?Sized> {
        generator: Box<G>,
    }
    

    And now it compiles.

    PS: If your Container struct is to be used only with Generator trait objects it is far easier to get rid of the generic arguments and just write:

    struct Container {
        generator: Box<Generator<Yield = Value, Return = ()>>,
    }
    
    impl Container {
        pub fn new(&mut self) -> Box<Self> {
            let generator = Box::new(|| loop {
                yield Value {}
            });
            Box::new(Container {
                generator: generator,
            })
        }
    }