Because there's (at least for me) usually a relation of the expression and the return value. If we add things like try, we'll add additional points into that relation.
Plus, the point was about "making sure things don't compile for half-refactored code" concerning error handling.
Of course, but that's not a good argument to me for making it more risky. If you change None to Some(None) you're actually changing logic. If you go from try to non-try or vice-versa, you're trying to not change the logic.
Sure, so do I, but that won't work for combining two traits that both have associated types that have to be combined and are Option<T>.
What do you think about the 23.into() case for Option<i32>?
While pure has been retracted, nobody seems to have mentioned one of the most fundamental intuitiveness issues it had:
Things like let, match, try!, catch, return, throw, raise, wrap, etc. are consistently verbs. pure is an adjective like public, mutable, or static, which generally implies it to be part of a type or function signature.
Having a consistent ruleset for relating programming language grammar and natural language grammar is the first layer that contributes to intuitiveness.
Also, since that comment could come across as an implicit āIām in favour⦠given the right bikesheddingā, Iāll clarify that, having read the entire thread, I share the fundamental concerns expressed by @chriskrycho and @H2CO3 and the mindset expressed by @ranweiler and @josh
I have to agree with that. Even having contributed to the discussion in several RFCs (eg. analogizing .. and ..= to < and <=), āPre-RFCā feels so formal that I still found myself caught up in a āthis is too important to risk waiting for an RFCā pseudo-panic and the only thing holding me back was the knowledge that I didnāt really have much to say that hadnāt already been said better by someone else. (Though it did lead me to stay up hours beyond bedtime, religiously liking the replies I agreed with.)
Perhaps just Idea: as the prefix? Itād still be a distinctive string but, at the same time, feels quite informal to me. (More in the vein of āThis came to me while I was on the bus. What does everyone think?ā)
It would also have the benefit of possibly increasing suggestions, since, not really knowing what the rules are for a āPre-RFCā, Iāve been operating under the assumption that Iām not even qualified to write one without embarassing myself.
(I have no background in language design and my programming experience has been results-oriented, so I never spent any time in ālanguage theoristsā playgroundā languages like Haskell or more than one single-semester beginnerās C/C++ class in āyouāll eventually either sink or figure out some aspect of the machine modelā languages.)
Another point to consider regarding implicit early return: while we do want to preserve ? in normal code, implicit early return could be useful in solving problems around ? in closures.
Treating try as an effect, a generic function could be made effect-polymorphic in order to pass through early returns without changing its interface in the infallible case. For example, you might write something like this:
cache.entry("key").or_insert_with(|| try {
let x = try_calculation()?;
process(x)?
})
If, as today, or_insert_with had to decide up-front whether its argument had type F: FnOnce() -> T or F: FnOnce() -> Result<T, E>, it could just use ? in its body when calling it. However, if it were instead something like F: ?try FnOnce() -> T, it could implicitly pass through Err returns from its argument without any changes to its body. (Alternatively we could have some kind of ?-only-if-try syntax, but that sounds pretty ugly.)
These kinds of combinators (most are probably iterator adapters) are generally pretty small and donāt do any of their own error handling, so the ability to pick out all the ?s may not be as important there. I also, again, think this would also be really useful for async functions to be able to keep using all these APIs.
I thought of a couple more questions about the problem that I donāt think weāve listed:
Do we want to see purely local information (e.g. try) in a function signature? This would break precedent with all existing notation in a function signature.
What are the tradeoffs of rightward shift with try blocks and functions.
For the second question, I think having try functions would mitigate rightward shift, though I personally donāt support the idea.
All of that led me to introspect a bit about try blocks and functions. For me at least, try imposes a mental burden ā additional context information that I need to keep track of in addition to the algorithmic context that I actually care about. Itās an extra layer that is between me and the basic types. I can see the usefulness of try blocks for control flow, but only if they are short like some of the examples in this thread so that I donāt have to keep track of the context for more than a few lines. Thatās part of what bothers me about try functions, I think. You have to read the entire function with that context in mindā¦
I stopped following this because I felt like this proposal "was just going to happen" and I'm not active on the forums so I wouldn't have a chance to step out in front of it. I'm very much in the same boat as Josh here. I love Rust for the fact that it makes me write better code and care about the right types. It makes me a better programmer and it helps me write better code. This proposal just feels like its watering that down and says "handling errors properly is annoying. I don't even want to unwrap() each line, I want to just unwrap all in one block. fwiw, I've been writing Rust for almost 2 years and I've contributed to the compiler here and there over the years. I've also maintained the Rust package for a number of distros along the way and advocated the language. I certainly read the RFCs that are out there but like I said I keep quiet but this post made me want to speak up. Not to be harsh but nothing I've encountered to date has turned me off of Rust quite like this proposal has.
Well, it wouldn't be part of the signature. It would be part of the definition, but there's already quite a lot of stuff there that's not part of the signature, notably everything related to the argument patterns: the argument name at all, whether it's mut, whether it's destructuring that argument, ...
Can you clarify exactly what refactoring you're talking about? There's lots of ways to write inference-dependent code that works in a bunch of contexts.
I definitely agree it should be unambiguous. A previous discussion about a feature like this talked about potentially having it as a coercion, but that would allow both return 4 and return None in an -> Option<i32>, which I certainly don't like. And those two cases get even worse in -> Option<Option<T>>...
I just wanted to say thank you to @withoutboats for this proposal. Having tried to champion a new Python stdlib feature that ultimately got shot down, I sympathize that it can be frustrating when you work towards an idea but other people donāt assign the same values to the cost function involved with adding the feature.
I think everyone can agree that proper error handling in any language is very difficult, but the computer science field as a whole has been making steps slowly in the right direction.
Consider C, where return values from something like write(socket, ...) can return either the number of bytes sent, or signal an error that an error has occurred (which can be inspected in another global value, errorno). Thereās no way statically to enforce that this error is read, must less handled correctly.
When I left work on Friday, I found that my C++ program had thrown an unhandled out_of_range exception. Since C++ doesnāt include stack traces or have something like ?, Iām in for some serious debugging to find even where that was thrown, much less fix it.
Rustās Result<>, ?, and panic! are (in my opinion) huge innovations here. They solve very real problems, but of course there is a cost associated with them (as @withoutboats has described).
My point to all of this is that we need proposals like this in order to push the field farther, because I think Rust is at the very forefront of systems programming technologies. They may not always pan out, but we shouldnāt discourage each other from continuing to explore this space. We of course must be conservative and cautious. Another excellent example (from @withoutboats of course) is the failure crate, which clearly innovates in this space.
So in summary, thank you to everyone for the proposal and feedback. Letās use this feedback to imagine new ways to improve error handling and make history.
Sorry, I used incorrect terminology... You're right about args, but somehow it doesn't seem the same to me... I'm not really sure why, though... I think it might be that pretty much all keywords in a definition are relevant to the caller. The exception is mut on args, and I honestly have never liked that either...
Hereās my humorous attempt at predicting the future:
The year is 2020, Rust has been used in production on many high-profile applications. But one thing has been an issue since 2018: Thereās no way to ? out of nested catch blocks.
As the user base grows, and a clear need for ? in nested catch blocks emerges, a proposal from 2018 is revived: labeled ?.
When the original proposal supporting ? with any labeled block is deemed controversial, it is then restricted to only work on catch blocks. This quickly gets accepted and stabilized, and all is well. Untilā¦
One year later, result-producing loops become an important construct in Rust. These simple-sounding constructs have the form:
However, thereās still no clear way to ? out of a function, besides putting a labeled catch block inside the function. To help with this, function definitions are now allowed to take the form:
I suspect that labeled break from blocks, which looks like itās going to be merged, will be sufficient. ? will work great in simple situations up through try blocks, and in the rare situations that you truly want wackier control flow, you can just fall back to break-with-value.
And for the function-level case we already have return!
Iām pretty sure we have neither some_file.read_exact(bytes) return? nor some_file.read_exact(bytes) break 'label?. Indeed, if we had them, the whole idea of catch blocks would be redundant, and we wouldnāt be having this conversation.
(I donāt know about you tho, but I still prefer ? and 'label? - no return or break.)