10

I'm trying to generate a vector containing lowercase ASCII characters. This more convoluted approach works:

let ascii_lowercase = (b'a'..=b'z').map(|b| b as char).collect::<Vec<char>>();

But this more straightforward one, which I came up with in the first place, does not:

let ascii_lowercase = ('a'..='z').collect::<Vec<char>>();

The error is:

error[E0599]: no method named `collect` found for type `std::ops::RangeInclusive<char>` in the current scope
 --> src/main.rs:2:39
  |
2 |     let ascii_lowercase = ('a'..='z').collect::<Vec<char>>();
  |                                       ^^^^^^^
  |
  = note: the method `collect` exists but the following trait bounds were not satisfied:
          `std::ops::RangeInclusive<char> : std::iter::Iterator`
          `&mut std::ops::RangeInclusive<char> : std::iter::Iterator`

Which is weird, because as far as I understand, there is a blanket implementation of Iterator for RangeInclusive.

Is it impossible to use a range of chars as an iterator? If so, why? If not, what am I doing wrong? I'm using stable Rust 2018 1.31.1.

1
  • 1
    You missed where A: Step Commented Dec 29, 2018 at 17:55

2 Answers 2

13

EDIT 2020-07-17: since Rust 1.45.0, the trait Step is implemented for char, making Range<char> (and some other char ranges) work as an iterator. The code in the question now compiles without problem!

Old answer below.


The expression b'a'..=b'z' has the type RangeInclusive<u8> (see on Playground) because the expression b'a' has the type u8: that's what the b in front of the character literal is for. On the other hand, the expression 'a'..='z' (without the bs) has the type RangeInclusive<char>.

[...] there is a blanket implementation of Iterator for RangeInclusive.

For one, this is not what we call "blanket implementation" (this is when the impl block is for T or for &T (or similar) with T being a generic type). But yes, there is an impl. But let's take a closer look:

impl<A> Iterator for RangeInclusive<A> 
where
    A: Step,   // <--- important

The A: Step bound is important. As you can see in the documentation for Step, this trait is implemented for all primitive integer types, but not for char. This means that there is no clear "add one" operation on characters. Yes, you could define it to be the next valid Unicode codepoint, but the Rust developers probably decided against that for a good reason.

As a consequence, RangeInclusive<char> does not implement Iterator.

So your solution is already a good one. I would probably write this:

(b'a'..=b'z').map(char::from).collect::<Vec<_>>()

The only real advantage is that in this version, char doesn't appear twice.

Sign up to request clarification or add additional context in comments.

2 Comments

@Shepmaster Good find. But I think that question is mainly about the endpoint and that answer just incidentally also mentions the char "problem". Having a dedicated question for "char iterator" is better IMO.
Thanks! I figured there must be a difference between chars and u8s I was missing, but I can't read Rust that well yet, so I wasn't able to figure out that the root cause of "RangeInclusive<char> doesn't implement Iterator" is that char doesn't implement Step. Thank you also for clarifying the usage of the term "blanket implementation", I'll be more careful in the future :) Re: the dupe, I think SO did suggest it to me when I was composing this question, but the title felt so clearly not like my problem that I didn't even click it...
3

The problem was that the iteration capabilities of range types depend on the Step trait (see extended answer). However, starting from Rust 1.45, char also implements Step (PR 72413), which means that the code in the question now works!

let ascii_lowercase: Vec<char> = ('a'..='h').collect();
assert_eq!(
    ascii_lowercase,
    vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.