Getting an Iterator<Item=str> from an array of &str?

Viewed 235

I'm trying to abstract a function to take both an instance of std::str::Lines and a mocked version for testing purposes, created out of an array of &str.

My code (which does work) looks something like this:

use std::fs;

#[test]
fn test_day_1() {
    let v = ["3", "3", "4", "-2", "-4"].iter().map(|x| *x);
    assert_eq!(day1(v), "334-2-4334-2-4");
}

fn day1_pre() -> String {
    let contents = fs::read_to_string("day1.txt").expect("error reading file");
    day1(contents.lines())
}

fn day1<'a>(lines: impl Iterator<Item = &'a str> + Clone) -> String {
    lines
        .map(|line| {
            let v: Result<i32, _> = line.parse();
            v.expect("could not parse line as integer")
        })
        .cycle()
        .take(10)
        .map(|x| x.to_string())
        .collect()
}

However, this code only works because of the weird .map(|x| *x) inside the test. If I remove that, I get the following error:

error[E0271]: type mismatch resolving `<std::slice::Iter<'_, &str> as Iterator>::Item == &str`
  --> src/lib.rs:6:16
   |
6  |     assert_eq!(day1(v), "334-2-4334-2-4");
   |                ^^^^ expected `str`, found `&str`
...
14 | fn day1<'a>(lines: impl Iterator<Item = &'a str> + Clone) -> String {
   |                                  -------------- required by this bound in `day1`
   |
   = note: expected reference `&str`
              found reference `&&str`

I sort of understand the error. iter returns an &T, which in this case yields a &&str. What I don't understand is why removing the map and replacing iter with into_iter (i.e. let v = ["3", "3", "4", "-2", "-4"].into_iter();) also fails with the same error!

According to the documentation, into_iter iterates over T, thus it should work here?

While writing this post, I also tried replacing the array with a Vec and using into_iter, such that the final result is let v = vec!["3","3","4","-2","-4"].into_iter(); and it worked! However, now I'm even more confused, why would into_iter work for Vec but not for Array?

2 Answers

This was announced with the Rust 1.53 release notes. IntoIterator for arrays is new addition that was implemented in 1.53 , but behaves differently in the 2018 and 2021 editions:

This was not implemented before, due to backwards compatibility problems. Because IntoIterator was already implemented for references to arrays, array.into_iter() already compiled in earlier versions, resolving to (&array).into_iter().

As of this release, arrays implement IntoIterator with a small workaround to avoid breaking code. The compiler will continue to resolve array.into_iter() to (&array).into_iter(), as if the trait implementation does not exist. This only applies to the .into_iter() method call syntax, and does not affect any other syntax such as for e in [1, 2, 3], iter.zip([1, 2, 3]) or IntoIterator::into_iter([1, 2, 3]), which all compile fine.

Since this special case for .into_iter() is only required to avoid breaking existing code, it is removed in the new edition, Rust 2021, which will be released later this year. See the edition announcement for more information.

Therefore your code will compile just fine with Rust 2021

In this case you could just take an iterator of anything that can be taken as a reference into a str (any &&&&&..&str should):

fn day1<T>(lines: impl Iterator<Item = T> + Clone) -> String where T : AsRef<str>{
    lines
        .map(|line| {
            let v: Result<i32, _> = line.as_ref().parse();
            v.expect("could not parse line as integer")
        })
        .cycle()
        .take(10)
        .map(|x| x.to_string())
        .collect()
}

Playground

And why would into_iter work for a Vec and not a slice, well, if you read the current warning when using into_iter:

warning: this method call currently resolves to `<&[T; N] as IntoIterator>::into_iter` (due to autoref coercions), but that might change in the future when `IntoIterator` impls for arrays are added.
 --> src/lib.rs:5:41
  |
5 |     let v = ["3", "3", "4", "-2", "-4"].into_iter();
  |                                         ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
  |
  = note: `#[warn(array_into_iter)]` on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #66145 <https://github.com/rust-lang/rust/issues/66145>

Follow to the issue link where there is a good explanation. Keypoint is:

[1, 2, 3].into_iter().for_each(|n| { *n; }); Currently this works, as into_iter returns an iterator over references to the array's values, meaning that n is indeed &{integer} and can be dereferenced.

Related