Regarding the question of whether we can do without any extra syntax at all, let's think about this in terms of our different kinds of users.
People learning or teaching Rust
In a comment above, I argued on behalf of novice users, and those that have to teach them. Clearly a Rust user at some point needs to learn the deep truths about how things like closures, iterators, and futures are implemented and treated in the type system. But I don't see the value in beating people down with compiler errors early on in the journey when the code could be made to work in a principled and straightforward manner with truly nominal and unavoidable overhead (more on that below). They can learn that Rust closures are hard at any time. No need to force it on people early.
Consider that Rust compiles this code today:
fn make_frob(x: u32) -> impl Fn(u32) -> u32 {
if x % 2 == 0 {
|y| y / 2
} else {
|y| y * 2
}
}
But obviously as soon as one of the (syntactic) closures captures a value, as in the earlier make_frob
s, it will break. The reasons for this are subtle, and I think everyone learning Rust should understand those things eventually. But it would be OK if the language let us teach those things later.
The average user
In terms of the effect this would have on the average user, I'm reminded of the work on non-lexical lifetimes (NLL) and improved match ergonomics.
In each of these cases, it could be (and was) argued that forcing people to be more explicit is better. But we instead decided to make large swaths of reasonable-looking code "just work." It seems that has been a success.
Regarding the NLL work, I recall specifically someone on the lang team pointing out, and I'm paraphrasing from memory, that "rejecting correct programs is not a desirable feature."
It seems to me the desire for extra syntax is born from a concern that the average user will not understand what's going on and will accidentally add some extra overhead. But if it's not too much to ask that the average user understand that, e.g., every closure has a different anonymous type (except things that look like closures but are not because they don't capture), then it's hard for me to see how it's too much to ask for that user to know that returning a different type from a branch will result in an anonymous enum.
Besides, what's the user supposed to do instead? If the user really wants to return two different types, then it's difficult to see what better option the user has.
People coming from auto-boxing languages
Anyone coming from Python, Javascript, Lua, Lisp, Haskell, etc. really wants make_frob
to just work the first and every time they try it. If a language like Rust can make the code these people want to write work without boxing, that's the good kind of magic.
The expert user
Experts will get used to whatever we do, and in particular, will know immediately and intuitively that returning a different type from a branch will result in an anonymous enum, just like these experts know immediately and intuitively right now that such code will result in a compiler error. Personally, I'd be happy that it did exactly what I wanted there with less typing.
Critically, this is not to say that all forms of explicitness are bad. Moving to dyn Trait
was a truly good change for a whole host of reasons that mostly don't apply here.
People concerned about churn in the language
Some people are concerned about the rate or perceived rate of change of the language. If we add new syntax, then all users need to learn it, even if they don't use it themselves, as they will surely run across it in the code of others. Conversely, if we just make code that looks like it should work actually work without new syntax, then someone unfamiliar with anonymous enums may be surprised that a particular block of code works, but they're unlikely to be confused about what that code means and what result it produces.
As happened with NLL, I expect most users surprised that a particular block of code compiles would think, "huh, that's neat, I guess Rust finally decided to accept the obvious code there."
People concerned about zero-cost abstractions
Stroustrup's rule for zero-cost abstractions is:
What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better.
Anonymous enums without extra syntax meet this standard. No code that compiles today incurs any additional overhead due to these enums. And if you're trying to return, e.g., two or more different types of closures, or iterators, or futures, then there is no way you could implement it any better than the anonymous enum.
In summary, I'm a big fan of being explicit in general, but if we can make code that looks like it should work actually work in a principled manner and in a way that doesn't add avoidable overhead, and if it's better for all or most of our kinds of users, then it seems that we should at least strongly consider just doing that.
P.S. Any syntax we don't add is syntax that we don't have to bikeshed.