1

This is a simplified example of my enum:

#[derive(Debug, Clone, Copy)]
enum Data<'a> {
    I32(&'a [i32]),
    F64(&'a [f64]),
}

I use the enum to store different slice types (not only &[i32] and &[f64] as in the example above, but many more) to the same vector Vec<Data<'a>>. I need a way to iterate through the slice values (either &[i32] or &[f64], whatever is stored in the enum) converting all values to String. The following code shows what in principle I would like to achieve, but it does not work:

impl<'a> Data<'a> {
    fn iter_to_string(&self) -> impl Iterator<Item = String> {
        match self {
            Data::I32(data) => data.iter().map(|&x| x.to_string()),
            Data::F64(data) => data.iter().map(|&x| x.to_string()),
        }
    }
}
error[E0308]: match arms have incompatible types
  --> src/main.rs:9:9
   |
9  | /         match self {
10 | |             Data::I32(data) => data.iter().map(|&x| x.to_string()),
11 | |             Data::F64(data) => data.iter().map(|&x| x.to_string()),
   | |                                ----------------------------------- match arm with an incompatible type
12 | |         }
   | |_________^ expected i32, found f64
   |
   = note: expected type `std::iter::Map<std::slice::Iter<'_, i32>, [closure@src/main.rs:10:48: 10:66]>`
              found type `std::iter::Map<std::slice::Iter<'_, f64>, [closure@src/main.rs:11:48: 11:66]>`
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
lucatrv
  • 725
  • 8
  • 14
  • @trentcl do you think, once reopened, this will be a duplicate of [Conditionally iterate over one of several possible iterators](https://stackoverflow.com/q/29760668/155423)? – Shepmaster Feb 18 '19 at 15:13
  • @Shepmaster Yeah, probably so – trent Feb 18 '19 at 15:21
  • The difference is that I need to convert all different types to a common type (in this example to `String`), I guess it is a common case every time one need to store different `Vec` types in the same `Vec` using an `enum`. If it gets reopened I can post an answer, otherwise if you think it is a duplicate please go ahead and delete it. – lucatrv Feb 18 '19 at 20:55

1 Answers1

1

You have two options:

  1. Dynamic dispatch
  2. A non-lazy iterator

You can't have both static dispatch and a lazy iterator, because that requires the compiler to produce the code path for to_string() at compile time where the correct choice is only known at runtime.

Dynamic dispatch

To use dynamic dispatch, you can create an iterator over trait objects.

use std::fmt::Display;

struct DisplayIter<'iter> {
  inner_iter: Box<Iterator<Item=&'iter dyn Display> + 'iter>,
}

impl<'iter> Iterator for DisplayIter<'iter> {
  type Item = String;

  fn next(&mut self) -> Option<String> {
    self.inner_iter.next().map(|val| format!("{}", val))
  }
}

In order to use it on a slice iterator, though, you'll need to transform each reference into a trait object reference, such as

data.iter().map(|x| x as &dyn Display)

Non-lazy iterator

Just collect the results of the map you're already doing, and return a owned iterator over the result. This has the downside of allocating, but it solves the issue of needing to choose the code path at compile-time.

data.iter().map(|&x| x.to_string()).collect::<Vec<_>>().into_iter()
Isaac van Bakel
  • 1,772
  • 10
  • 22