Iterator take_while or filter - Double dereference - why with Iter but not with RangeFrom or Range

Code with Iter:

let values_for_iter = [0i32, 1, 2].iter();
let mut iter_result = values_for_iter.take_while(|x| **x < 10 );
println!("values result is {:?}", iter_result.next());

Code with Range or RangeFrom:

let values_for_range = 0..;
let mut range_values = values_for_range.take_while(|x| *x < 10 );
println!("range values is {:?}", range_values.next());

Why double derefence not needed in Code with Range?

That would be because Range<i32> is an Iterator<i32>, whereas [i32]::iter() returns an Iterator<&i32>.

The reason for this discrepancy, in turn, is that iterating over a range consumes it (= destroys it), whereas iterating over an array does not consume it. If you do not consume the array, you can only return references to the elements, not move them away. Hence the Iterator<&i32>.

In an ideal world, you could consume the array and get an Iterator<i32> just by calling into_iter() on it instead of iter(), as done on Vec<i32>. Sadly, though, this implementation of IntoIterator was not included in the Rust standard library back when the language was stabilized, and as far as I know it cannot be added in a backward-compatible way so we're unlikely to ever see it...

For small types like i32, however, you can work around this by making inconsequential copies which will likely be optimized out by the compiler, via [1, 2, 3].iter().cloned().

1 Like

Since 1.36 shouldn't you use iter().copied() to be more explicit that you want inconsequential copies only?

1 Like

Oh, that's stable now? Then yes, it's better to use it if old rustc compatibility is not needed. :wink:

1 Like

Thanks @HadrienG, so writing the same as above with Types I do see:

let values_for_iter: core::slice::Iter = [0i32, 1, 2].iter();

let values_for_range: core::ops::Range = 0..10;

The slice suggests the borrowing happens with Iter.

How did you see Iterator and Iterator<&i32> types? Is there a type function I could use in code directly to see the difference?

In the end, using take_while or filter giving a more consistent usage to functions from the same Iterator trait would be great.

I did not see it, I knew about it beforehand because I fell in the same trap that you did many times :wink:

Anyway, to answer your question, you can check that you're dealing with the kind of iterator that you expect to be dealing with by abusing generics / impl Trait a bit:

fn assert_iterator_i32(_: impl Iterator<Item=i32>) {}

// Will succesfully compile
assert_iterator_i32(0..10);

// Will cause a compiler error that contains the actual iterator type
assert_iterator_i32([0i32, 1, 2].iter());

In future Rust, you'll be able to use this little trick even more quickly via impl Trait bindings:

// Not in stable Rust, but scheduled for future Rust versions
let check: impl Iterator<Item=i32> = [0i32, 1, 2].iter();

Got it @HadrienG but Sorry my question was more towards if you did not know the Iterator<&i32> was being returned?

How to go from

let values_for_iter: core::slice::Iter = [0i32, 1, 2].iter();

to core::iter::Iterator<&i32>

since I know core::slice::Iter?

Essentially was looking for a simple type function here that directly tells the type being used or taits implemented?

Thanks @chrisd for the copied suggestion. With that double dereference is not required.

let values_for_iter = [0i32, 1, 2].iter().copied();
let mut iter_result = values_for_iter.take_while(|x| *x < 10 );
println!("values result is {:?}", iter_result.next());

looks like @HadrienG, there is no direct way to inspect type :cry:
But thx for all the answers.

As far as I know, you have two options:

  • Check the documentation of core::slice::Iter. After some digging, you will eventually figure out that calling iter() on an array of T dispatches (via AsRef) to the slice method [T]::iter(), which in turn returns an object that implements Iterator<Item=&T>.
  • Keep shamelessly using dirty impl Iterator tricks like the one I pointed out above. The compiler's error message, graciously sent to you by rustc's powerful trait engine, will tell you the actual item type of the iterator even if it's not a simple T vs &T matter.

I am not aware of an easy way to ask rustc for a full list of traits implemented by a type. Even if there were one, you probably wouldn't want to use it, because for some types that list can be very long, and grow anytime you add extra crates to your Cargo.toml...

Yeah, what I was looking for full list of all traits and I could grep those based on pattern.
I guess that is not there.
This was to save time instead of digging in for every such issue.
May be someday rust team could have such a function but I am guessing there are reasons why it is not there yet.

Is there a function that tells something is a reference vs moved ownership directly?
May be that would solve some of the issues.

Just make your best guess. If you guessed wrong, the compiler will tell you it got a &i32 but expected an i32 or vice versa, so it'll be rather easy to spot what is wrong. At some point you will get familiar with the iterators, and your guesses will typically be right.

Regarding reading the documentation, almost every other example will have a doc, where finding the type will be easier.

@alice: compiler and editor are great at helping to resolve the issue from point of view of reference being used or not. That part is easy - to make it work.

However, my intent was to know 'easily' exactly which Trait is forcing this to happen so my understanding of the codebase is more firm and not random :sunglasses:
The point is not to make guesses and quickly trace the Trait involved.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.