What might fix this is a special-case rule where an implicit¹ & or &mut reference as a method receiver is temporarily ignored while the arguments are being evaluated (with the borrow checker double-checking after the arguments are evaluated that the & or &mut reference is still legal, i.e. that the referenced value wasn't moved and, in the case of a &mut reference, that no other borrow exists as a result of evaluating the arguments). This rule, being a special case, doesn't require non-lexical lifetimes.
¹I say implicit because it would be potentially confusing for an explicit reference to be ignored while the arguments are evaluated.
let mut nums = Vec::new();
nums.push(nums.len());
but disallow let mut nums = Box::new(Vec::new());
nums.push(nums.len());
The second example would have to be disallowed because the mutable reference is created by calling DerefMut::deref_mut().In retrospect, changing the evaluation order of a function with respect to its arguments might have been a better choice, but something like the solution I mentioned is probably the only backwards-compatible option.
One of the ergonomic hurdles to rust adoption by experienced C++ users is that the language looks similar enough that such people may think they fully understand the semantics even before they've fully internalized all the details. It's a tricky zone when something feels familiar enough that you don't realize the remaining mistakes in your conceptualization.
This output is improving, too https://github.com/rust-lang/rust/pull/32756 -- feedback appreciated!
There are no current plans for a Rust 2.0.
Nice to read. Are there any other such improvements planned?
Doing mostly scripting languages, Rust feels often a bit clunky with solutions like you mentioned or its unwrapping etc.
There's also a huge overhaul to compiler error message formatting that should make error messages friendlier and more informative, and should hopefully be landing in nightly sometime today: https://github.com/rust-lang/rust/pull/32756 .
Also, part of the same work that is enabling non-lexical lifetimes (MIR, discussed here last week: https://news.ycombinator.com/item?id=11581681 ) is also going to enable incremental recompilation of Rust code, which should have a dramatic effect on turnaround time when developing code, as you'll spend enormously less time waiting for things to compile.
So now instead of dealing with errors or none "at the boundaries", the codebase is shot-through with uncertainty just to get access to a less-clunky syntax.
It also doesn't solve silly things like io::stdout().flush().unwrap(), where you have to unwrap an empty return value, because there's nothing sane to do if it's not Ok(()) other than panic. But the only thing that would really solves those ergonomics is proper exceptions.
edit: And if every function is just going to be defined as returning Result<foo> instead of foo it feels like the language might as well bite the bullet and add a fn definition syntax that sugars the endless repetition of -> Result<foo> just for readability's sake.
> So now instead of dealing with Nones "at the
> boundaries", the codebase is shot-through with
> uncertainty just to get access to a less-clunky syntax.
I'm not sure what this is referring to. The try macro and ? operator are solely for propagation, it doesn't make sense to speak of someone restructuring their code "just to get access" to them. Either you were propagating an error before, or you weren't. And if you weren't propagating it that means that you were handling it, which, if you're capable of doing so, is certainly better than propagating.As for exceptions, having them is a non-starter given that Rust intends to be useful in environment where unwinding is simply unavailable. Both Dropbox and Firefox make use of Rust code compiled without any unwinding support. So you can either bifurcate your ecosystem with factions that alternately support errors-as-exceptions and errors-as-return-values, or you can rally around a single one and work to make its usage as nice as you can.
It's about ergonomics. The concern is what happens when a junior dev hits some Result<> they don't really know how to deal with. Something like this:
io::stdout().flush().unwrap()
This kind of sucks. The "verb" of this line of code is flush(), and in most languages the verb of a line of code is the last thing on it. It's what people are used to scanning for. .unwrap()s bury it in the middle of the line, which makes code harder to scan for verbs. Additionally, everybody has "unwrap is a code smell" chanted at them by Rust people "in the know".So I want to make this go away, but there isn't a sensible thing for me to do if the universe is fucked to the point where flush isn't working, so matching on the result feels like enormous overkill because I don't really know what to do with the error.
io::stdout().flush()?
Reads a hell of a lot better. It communicates what I'm actually trying to do more simply, and scans a lot better. Of course, it only works in functions that return Result<>s, because it's a Result propagation mechanism, not a do-or-die mechanism.So if I'm a junior programmer, coming from some less strict language, a bit confused about all of this and I want code to read the way I expect it to read coming from other languages, I might start sprinkling in ?s every time I have to deal with these "Result" thingys. Of course, then the compiler yells at me because my caller has to unwrap Results, so I sprinkle in some more ?s and change my return type, and then...etc.
Pretty soon every function in the codebase returns a Result<> just so the code stays more readable and they can punt errors to main and exit, like they're used to.
tl;dr I think we're likely to see junior devs using a lot more Result<>s than they need to, because ? is more readable and "don't use unwrap" is Dogma.
Not sure how well this generalizes to your broader complaint. One could argue that in general, you really do need to think about errors to write good software, so even if languages with exceptions make it slightly easier to write of code that doesn't, that's actually an antifeature because it encourages sloppiness. But maybe that's overly punitive rather than pragmatic. Still, I don't think the answer is exceptions.
[1] preferably because of course other errors are possible, especially if stdout is redirected to a file on disk - but then, in a quick and dirty program, if you ignore errors, you're doing no worse than umpteen million C programs that call printf without checking the return value.
But you could make the case that I'm being overly pessimistic in focusing on the worst case mess that could be made with this feature.
Eg) plenty of people posting code snippets asking for help in the usual places (/r/rust, the forum, SO) will immediately have 5 commentors jump down their throat if there's an unwrap in sight.
Unwrapping may be acceptable in application code - it depends on your application. Specifically it depends on whether you need specific handling of unexpected events.
If your program is a 'script' - by which I mean something that executes for a short period of time to perform a series of tasks and then exits - then panicking on an error is a reasonable way of aborting.
On the other hand, if it's a daemon, then you will likely want to gracefully handle all error conditions and carry on.
I think the issue here is that Rust is primarily used as a systems programming language and usage tends more to fall in the 'daemon' category. In that mentality you handle every error (even if it's just to print something), and that is probably where the general community pointers towards avoiding unwrap come from.
Try to flush other files, finish off transactions, remove potentially hanging locks, alert on stderr or in logs, etc. I don't think there isn't anything sane to do. Every app should have some global "something's fucked, try to clean up as much as possible" code path.
Due to Rust's low-level nature, we've focused on the low-level stuff first: the higher-level APIs can then be built on top. I think this kind of thing would be more helpful to you than actual language improvements in many cases.