A small adventure in the standard library

While writing some machinery for dealing with spans on built-in attributes last month, I was slightly bothered by a missing method on Iterator. I usually work on the compiler, not the library, but I decided to write a little proposal, which was then accepted, so today it was time to try and implement it. In fairness, that implementation itself was mostly trivial, and yet right now there's no way to stabilize it. What I actually want to share is the incredible compiler work going on as we speak that will unblock it.

Press for table of Contents

The feature I’d like

This is not the first time I ran into this: let’s say you have an api that returns a vector of elements:

1
fn get_all_spans() -> Vec<Span> { ... }

Then sometimes you want to do something when exactly one element is returned. You could write:

1
let mut elems = get_all_spans();
2
if elems.len() != 1 {
3
return None
4
} else {
5
return Some(elems.pop().expect("we just checked the length"))
6
}

The expect is a bit unfortunate though. Maybe this is nicer?

1
let mut elems = get_all_spans();
2
elems.pop().filter(|| elems.is_empty())

There are certainly ways to remove the mutable variable too, maybe with iterators. Still, none of those solutions are particularly pretty. I think this is common enough that it’s something the standard library could provide. Maybe not on Vec, or &[], but on iterators so it’s widely applicable. That’s what I proposed in rust-lang/libs-team#676 , with which we could write the solution as:

1
get_all_spans().into_iter().exactly_one()

Stabilizing methods on traits

The implementation is rather trivial, this is the PR if you’re interested anyway: rust-lang/rust#149270 . The problem is that itertools already has a method named exactly_one. With a different signature. And a blanket implementation for T: Iterator.

So, there exists perfectly valid Rust code, in production right now, that looks like this:

1
// import the trait with a blanket implementation
2
use itertools::Itertools;
3
4
match foo.into_iter().exactly_one() {
5
Ok(_) => ...,
6
Err(_) => ...,
7
}

Which will break as soon as we stabilize the method on Iterator which instead returns an Option. And that’s not just a problem with Iterator. Any trait method we stabilize, that someone already added themselves through an extension trait with a blanket implementation, is a breaking change. Though the problem with methods we want on Iterator, which already existing in itertools is apparently a common one.

Not that this clueless girl knew that this morning. I only found out when CI failed on the implementation PR: the compiler itself couldn’t compile with the updated standard library containing exactly_one because of a warning about a name resolution ambiguity. We use itertools and its existing exactly_one method too, causing the exact same problem users will when we stabilize it.

The good news is: this is being fixed! And if like me, you didn’t already know about this: you should! It’s cool! These internal compiler changes aren’t always super visible, but the result is that we can stabilize parts of the standard library which are things you might notice.

Only 3 weeks ago amanieu's github avatar amanieu opened the stabilization PR for a feature called supertrait_item_shadowing rust-lang/rust#148605 which makes changes rust’s name resolution rules to:

And guess what, no less than 3 blocked PRs are mentioned about adding methods to Iterator!

This whole issue reminds me of the issue we have with built-in attributes. Technically, every built-in attribute we add to rustc is a breaking change… I expect some day we need a similar change in name resolution there.

So, to end off, let me repeat the acknowledgements from under that stabilization PR, by thanking compiler-errors's github avatar compiler-errors and lcdr's github avatar lcdr for making this possible.