1

It seems to me that whether an option is the right type to return should be up to the implementor.

I notice that it goes away when I try to filter or using other collection methods on the items. Is this simply a replacement for has_next? Won't it have potential performance/memory implications?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
thoredge
  • 12,237
  • 1
  • 40
  • 55
  • 3
    What should `[].iter().next()` do? – trent May 25 '18 at 11:59
  • 2
    "and won't it have potential performance/memory-implications?" no more that any < size or != NULL or similar. – Stargateur May 25 '18 at 12:11
  • @trentcl I guess it's a priority in Rust to avoid panic on an exhausted iterators. Other languages use has_next and then panics on next. It just feels like a big overhead on a quite low-level construct. – thoredge May 25 '18 at 12:14
  • @Stargateur If you create an iterator over bytes (u8) you will have created as many Somes has you have bytes. – thoredge May 25 '18 at 12:18
  • 1
    @thoredge no, it's an additional 8 bytes at maximum (and often even 0) for **one** entry at a time; moving to the next `Item` drops the previous one. – ljedrz May 25 '18 at 12:19
  • [What is the overhead of Rust's Option type?](https://stackoverflow.com/q/16504643/155423) – Shepmaster May 25 '18 at 13:56

1 Answers1

8

Because it needs some way to communicate to the caller that there's nothing left to output.

fn main() {
    let mut it = vec![1, 2, 3].into_iter();
    assert_eq!(it.next(), Some(1));
    assert_eq!(it.next(), Some(2));
    assert_eq!(it.next(), Some(3));
    assert_eq!(it.next(), None); // End of iterator.
}

As for a hypothetical has_next, that can complicate some iterator designs because it requires the iterator to know whether there is another element. This might require the iterator to compute the next element, then store it somewhere. It's also possible to forget to call has_next, or call it but ignore the result.

With next returning an Option, none of this is an issue; an iterator can compute the next item and return it whilst making it impossible for a caller to forget to ensure the returned value actually has something in it.

One thing this does not let you do is "peek" at the iterator to see if there's something more and then change logic based on that answer, without actually consuming the next item. However, that's what the peekable combinator is for, which gives you what amounts to a traditional has_next: peek().is_some().

On your concerns about performance: I've never seen anything to suggest there is any penalty. Anything using an iterator correctly has to check to see if it's reached the end. As for space, a Rust iterator doesn't need to cache the next item, so they're likely to be the same size or smaller than an iterator for a language that uses has_next.

Finally, as noted in comments, Option is not heap allocated. A None is equivalent to a false followed by some uninitialised space (since there's nothing in it), and a Some(v) is equivalent to a true followed by v.

DK.
  • 55,277
  • 5
  • 189
  • 162
  • My point is it feels like a very expensive way to do it. If you create an iterator over bytes (u8) you will have created as many Somes has you have bytes. – thoredge May 25 '18 at 12:20
  • 4
    @thoredge: And if you use `has_next`, you will have created as many `bool`s as you have bytes. – DK. May 25 '18 at 12:20
  • 1
    @thoredge: Wait a second, are you under the impression that `Option` is heap allocated? – DK. May 25 '18 at 12:22
  • More like a conditioned intuition ;-). Yes that is probably what was nagging me. The Some with the i32 (for example) will reside on the stack until picked up by the caller; is that right? – thoredge May 25 '18 at 12:33
  • 2
    @thoredge An `Option` is essentially (as far as layout is concerned) a `(bool, i32)` tuple. The `bool` and `i32` will in probably be returned in registers, but things larger than `i32` might be reserved on the caller's stack and written directly there; [this question](https://stackoverflow.com/questions/27835375/can-i-efficiently-return-object-by-value-in-rust) has more info. – trent May 25 '18 at 12:46
  • 2
    @thoredge: Also, if the `T` in `Option` is a type that won't ever have an all-zeroes bit pattern, [the compiler can optimize the tag out entirely](https://stackoverflow.com/questions/46557608/what-is-the-null-pointer-optimization-in-rust). For example, `Option<&Foo>` is the same size as `&Foo`. – Joe Clay May 25 '18 at 13:57