Also, iter::once() is an Iterator, whereas Option is not. This makes it (marginally) nicer when you immediately want to use it as an iterator, e.g. chain() something onto it. With Some(_), you'd need an explicit .into_iter() for that.
In fact, std::iter::Once seems to be a newtype for std::option::IntoIter, see source:
/// An iterator that yields an element exactly once.
///
/// This `struct` is created by the [`once()`] function. See its documentation for more.
#[derive(Clone, Debug)]
#[stable(feature = "iter_once", since = "1.2.0")]
pub struct Once<T> {
inner: crate::option::IntoIter<T>,
}
So the question is: why a newtype pattern here, and not a type alias?
To demonstrate the implications:
use std::iter::Once;
use std::option::IntoIter as AltOnce;
fn does_not_compile() {
let _: AltOnce<()> = Some(()).into_iter(); // compiles
let _: Once<()> = Some(()).into_iter(); // doesn't compile
}
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/lib.rs:6:23
|
6 | let _: Once<()> = Some(()).into_iter(); // doesn't compile
| -------- ^^^^^^^^^^^^^^^^^^^^ expected struct `std::iter::Once`, found struct `std::option::IntoIter`
| |
| expected due to this
|
= note: expected struct `std::iter::Once<()>`
found struct `std::option::IntoIter<()>`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error
Once and AltOnce (as well as OnceTwin and AltOnceTwin) are incompatible (i.e. distinct) types. I don't see the value in that. They don't need to implement traits differently (I believe?).
Considering how fundamental the Option data type is, I wonder if the newtype could be replaced with a type alias to allow for more flexibility for the user of the library. Maybe that's a topic for IRLO though. I understand the motivation now.
The existence of the Once struct is not technically necessary.
And there were people in the RFC discussion mentioning that Some(x).into_iter() already works. But it doesn't look like anyone felt terribly strongly about this at the time.
So now that the actual history is out of the way, I'll say something about the newtype. Looking at it with fresh eyes, I'm pretty certain we'd choose the same result today as folks did back in 2015. Namely:
The newtype approach is the "conservative" approach. If there was ever any difference in semantics or otherwise between Option<T>::into_iter and std::iter::once, then we'd have some flexibility to maneuver there because they aren't tied together at the API level. It's hard to think of what that difference could be, and I can't think of one presently. But we do tend to be quite conservative because std's API needs to last for approximately forever.
There's also a question of API sensibility here. Having a free function in the std::iter module return a type that is defined in the std::option module is... pretty weird.
In any case, as the RFC discussion mentioned, there are indeed multiple ways to achieve this. Much more than 2. For example, std::iter::repeat(wat).take(1). And more recently, the quite terse [wat].into_iter() now works too. With [wat] working, I do wonder if we still would have added std::iter::once. But either way, you have many options to choose.
Personally, if it were me, I'd use the operation that most closely matches your intent. That's probably std::iter::once.
Also, replacing the return type of std::iter::once with a type alias to a different type is almost certainly off the table. Probably on the grounds that there isn't sufficient motivation for such a thing. And it also feels like something that could be a breaking change, although I just woke up and can't pinpoint why exactly. (One possible reason is that the trait impls for the two types don't match exactly, but I'm not going to go through them and check.)
I guess (just talking hypothetically), that std::option could refer to std::iter though, in regard to iterator-related things, namely setting <Option as IntoIterator>::IntoIter = std::iter::Once. Then Once would need to be implemented differently though. But I see how this increases dependencies between the modules.
Language design aside (I understand it's staying as it is), …
So use std::iter::Once when it's about what I want to achieve and use std::option::IntoIter when it's about how it's achieved?
I'm not sure if that's the right phrasing. Maybe it is. But to add a little more clarity, I personally wouldn't use std::option::IntoIter unless I already had an Option<T> in hand for some other reason. It's same for something like std::vec::IntoIter. I don't use that unless I already have a Vec in hand. Think of Option<T> as a collection that contains at most 1 element, and into_iter() is how you iterate over that collection. Given some Option<T>, iterating over it may not produce 1 element, it might actually produce 0 elements.
That's what I meant with using it when it's about "how" it's achieved (namely because I have already an Option<T> in hand.
Note though, that I can also construct a function that returns std::iter::Once<T> which might actually produce zero elements as well (it's a bit conceived contrived though):
fn never<T: Default>() -> std::iter::Once<T> {
let mut iter = std::iter::once(Default::default());
iter.next();
iter
}
fn main() {
let mut iter: std::iter::Once<String> = never();
assert_eq!(iter.next(), None);
}
This would be awkward indeed. So I think I understand now why std::iter::Once and std::option::IntoIter are two distinct data types. They kinda serve a different purpose. (I guess.)
std::option::IntoIter is an iterator that returns one or zero items
std::iter::Once is an iterator that returns one item (though it will return zero items after next has been called once).
But would it then be more idiomatic to use once here:
As in:
/// Extends a collection with exactly one element.
#[unstable(feature = "extend_one", issue = "72631")]
fn extend_one(&mut self, item: A) {
- self.extend(Some(item));
+ self.extend(std::iter::once(item));
}
Not that it really matters. Just asking to better understand code style / how to do it idiomatically. Maybe both choices are alright.
That would be a breaking change, because someone could have their own trait that they implement separately for each of those, which would become overlapping implementations if we aliased the types. I think that scenario is unlikely, but it's possible.