2
votes

The general setup is I have an array of values I'd like to map() and then chain() with 1 additional value. I've learned from this answer that the proper way to construct that final value is to use std::iter::once. This works and eliminated the below problem, but I would still like to understand it better.

In my broken, likely rust-anti-pattern-riddled example, I was using an array of a single element and then calling into_iter(). This produced a value / reference type-mismatch in the chain.

Question: What is the Rust-idiomatic mechanism for correcting this value / reference mismatch? Particularly if clone and copy are unavailable.

Background: Why is there a type mis-match to begin with?

This much I believe I understand. Based on the definition of std::iter::Map, the item type for the iterator is type Item = B where B is constrained by F: FnMut(<I as Iterator>::Item) -> B (i.e. the mapped type). However array defines the following 2 IntoIterator implementations, both of which appear to produce references.

impl<'a, const N: usize, T> IntoIterator for &'a [T; N] where
    [T; N]: LengthAtMost32, 
type Item = &'a T

impl<'a, const N: usize, T> IntoIterator for &'a mut [T; N] where
    [T; N]: LengthAtMost32, 
type Item = &'a mut T

Example demonstrating the issue:

#[derive(PartialEq, Eq, Clone, Copy)]
enum Enum1 {
    A, B, C
}

#[derive(PartialEq, Eq, Clone, Copy)]
enum Enum2 {
    X, Y, Z
}

struct Data {
    // Other data omitted
    e1: Enum1,
    e2: Enum2
}
struct Consumer {
    // Other data omitted

    /** Predicate which evaluates if this consumer can consume given Data */
    consumes: Box<dyn Fn(&Data) -> bool>
}


fn main() {
    // Objective: 3 consumers which consume data with A, B, and X respectively
    let v: Vec<Consumer> = [Enum1::A, Enum1::B].iter()
        .map(|&e1| Consumer { consumes: Box::new(move |data| data.e1 == e1) })
        // This chain results in an iterator type-mismatch: 
        // expected &Consumer, found Consumer
        .chain([Consumer { consumes: Box::new(move |data| data.e2 == Enum2::X) }].into_iter())
        .collect(); // Fails as well due to the chain failure

}

Error:

error[E0271]: type mismatch resolving `<std::slice::Iter<'_, Consumer> as std::iter::IntoIterator>::Item == Consumer`
  --> src/main.rs:52:10
   |
52 |         .chain([Consumer { consumes: Box::new(move |data| data.e2 == Enum2::X) }].into_iter())
   |          ^^^^^ expected reference, found struct `Consumer`
   |
   = note: expected type `&Consumer`
              found type `Consumer`

Rust playground example.

1

1 Answers

2
votes

There is a long-standing issue regarding this. The technical details are a bit heavy, but essentially, due to underlying, technical reasons, you cannot take ownership of a fixed-size array and return owned references without a lot of hocus pocus. This becomes obvious when you think about what a fixed-size array is and how it is stored in memory, and how you can get elements out without cloning them.

As a result, due to the implementations you found already, you can only get borrowed references. You can bypass this with arrayvec (as they have a sound implementation of IntoIterator for ArrayVec with owned types), or you can require that all your T: Clone and deal with it that way, at a cost of extra items in memory (temporarily; 90% of the time the compiler optimizes this away).