How do you store the result of an `impl Trait` return type?


#1

I have a function like:

fn foo() -> impl Iterator<Item=i32> {
  (0..10).map(|i| i*2)
}

If I want to store the resulting iterator in a struct, this seems like the natural definition:

struct Foo {
  foo : impl Iterator<Item=i32>,
}

impl Foo {
  fn new() -> Self {
    Foo {
      foo : foo()
    }
  }
}

I’d think of this an existential type with a trait bound, which seems like a natural dual to the impl return type.

The compiler disallows this syntax though. I feel like I must be missing something because it seems like I’m back to the choice I had before we had impl trait return types: do I use a Box, or “leak” types into the struct definition (and propagate those upward if Foo is itself stored in a struct or type-ascribed somewhere) even though it will only reasonably ever have one value?


#3

Using impl Trait outside of returns is still in the works, but in the meantime the easiest way is to make Foo generic over some I: Iterator<Item = i32>.

(playground)


#4

impl Trait isn’t one data type. Each different function with the same impl Type returns a different data type. Annotating the type onto variables is still listed as todo. ((without checking))

If dealing with single functions then like @Michael-F-Bryan states you make the struct generic. If needing runtime polymorphism then go with Box.


#5

You can always write a custom struct, implement Iterator explicitly, and use that. As long as the underlying value you’re working with is “namable” (ie doesn’t use closures) then this is a perfectly valid, if a bit boilerplate-y, approach for some cases.


#6

Thanks for the replies all, I’ll try to address them all:

@Michael-F-Bryan Parameterizing Foo “leaks” the type of this iterator into the public interface where it really should be private - a holder of a Foo shouldn’t know or care about this detail of its non-public implementation. It also “pushes the problem up” - now the holder of Foo needs to figure out how to name this thing or elide it (which often isn’t a huge problem, but can be annoying e.g. if Foo itself is stored in something).

@jonh: I edited the original post to make it clearer that the value of the field is only ever the result of foo, not a publicly-settable value (i.e. it should always be the same type). The trade between generic structs and Box isn’t just whether I want runtime polymorphism - Box is easy to drop in and have it “just work”, without dealing with the viral spread of generic parameters to holders of Foo. Box lets me keep the type of the iterator encapsulated inside the module (where adding a generic parameter means changing code for most things that use Foo), but it’s odd that I have to add allocations and runtime costs to do that.

@vitalyd: True, I forgot that option. That’s what I have right now in playform/common/surroundings_loader.rs. Now that impl trait landed, I was hoping to replace it with something bjorn3 wrote in a PR:

fn surroundings_iter(center: Point3<i32>, max_distance: i32) -> impl Iterator<Item=Point3<i32>> {
  (0 .. max_distance).flat_map(move |radius| cube_shell(&center, radius))
}

Thanks all, I was curious if there were any new solutions to the problem of storing complicated iterators since impl trait landed, but it sounds like the same options as before.


#7

Hmm, I tried to trick it into giving me it, but no luck, since it won’t let me name foo’s type (even though it has a distinct one):

#![feature(conservative_impl_trait)]

fn myfunc() -> impl Iterator<Item=i32> {
  (0..10).map(|i| i*2)
}

trait RetHelper {
    type Return;
}
impl<F:FnOnce()->R,R> RetHelper for F {
    type Return = R;
}

type ReturnType<F> = <F as RetHelper>::Return;

struct Foo {
  foo : ReturnType<myfunc>,
}

impl Foo {
  fn new() -> Self {
    Foo {
      foo : myfunc()
    }
  }
}

fn main() {}

#8