-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Error conventions #220
Conversation
although returning-assert() would be surprising to learners, it's worth it for one reason: there would be just two failure rules instead of 3. assert! and .assert() (as well as .assert_msg()) would be the names signalling failure (panic), which is very clear. |
Renaming
|
I'm somewhat in favour of this, but we are almost at a point where we don't need failure in the language, the compiler currently only inserts fail calls for indexing (which However, we do have failure related lang items like |
If #204 is accepted, then it would be pretty to handle "Out-of-bounds" and "key-not-found" by always returning an option (but I don't know if the compiler can optimize this, and it does make the code slightly more verbose). let x = @vec[1i,2,3,5];
let _y = x[3]!; |
I was against Between |
I would still prefer |
+1 for having sugary indexing (and slicing) that returns an (The desugaring will need some work though, because IIRC |
I think something very important that the RFC currently neglects to mention is that all functions that can currently fail, will have to return something with an I'm also wondering how out of memory errors would be handled (lots of failure in e.g. |
This looks great! "Panic" is nice and scary, "unwrap" sounded too benign for something that causes panic, and I like the convention of not using assertions to sanitize function input.
|
@Florob You're right, functions that currently fail would need to use As far as out-of-memory and stack overflow errors, I believe these both abort the process currently; see this issue for example. In the future they may |
Ah ok. |
I don't think I agree with lumping contract/programmer errors with other errors. Firstly, unlike what is claimed by the RFC, it does not seem hard to me to cleanly define what a contract/programmer error is. Every function will have a set of pre-conditions and post-conditions that specify a contract. In terms of pre-conditions, those can be clearly documented and if a programmer violates them by passing invalid input, then the said contract is violated. Contract errors are bugs that I want to eliminate from my program. It's convenient to have them Other errors, on the other hand, are things that I am expected to handle as part of a bug-free program operations. I want to be able to handle them easily. Consider this realistic example. I have a graphics library function that creates a texture of a given size: fn create_texture(w: uint, h: uint) -> Result<Texture, Error> That function won't return a valid texture if an invalid size is passed (0 for either dimension) or if there's insufficient GPU memory (amongst other issues). Now, let's say that I know for sure my size is valid (e.g. I use integer literals), how would I assert that contract? It would be inappropriate to So what I'd find more convenient would be something more along the lines of the current guidelines... i.e.:
I don't find the subjectiveness of the first option vs the second option to be an issue. Convenience functions are omnipresent in APIs and this would be one more place for them. |
I sort of agree with @SiegeLord here. "Error" is an ambiguous word. It's important not to mix up contract errors (i.e. programmer mistakes) and reasonable but unsatisfactory results. Assertions
The caller must be able to rely on the calleeAn unexpected The callee must be able to rely on correct usageIf you're not able, with program-flow logic, to ascertain that arguments to a function are valid you add a condition before calling. Released software should not be expected to have duplicate checks - both by the caller, and the callee - which is why assertions are debug-time only. Type systemIdeally you want to catch as many types of incorrect arguments as possible by restricting the input-domain using the type system. This makes it impossible to provide invalid arguments and makes the error checks unnecessary. They can be safely omitted and the code will be clearer. One of the supposed goals of Rust is to eliminate as many errors as possible statically. |
unwrap_or_fail is still the best name IMO |
assertions cannot be merely debug assertions, because ignoring them may lead to memory unsafety. From my point of view, bugs can never be eliminated, and assertions are a way to quickly detect the bug and bring down a program safely. If contract assertions are
I think this is just an argument for keeping unwinding (transactions can then be finilized by destructors). If unwinding is removed, then yes... I'd agree this may be important to do. That said, the current RFC allows for failure, and in fact will have exactly as much failure as what I propose (assuming people don't paper over contract violations with logic), so it's a general criticism. If we are to have failure at all, there is a possibility that it won't interact well with these transactions. |
Transactions are not supposed to the finalized during failure, they're supposed to be rolled back. Explicitly rolling back a transaction is unnecessary if the process is just exiting immediately, because that also results in it being rolled back. That's the point of a transaction after all. |
It's pretty trivial to handle transactions correctly with respect to failure in either an unwinding or aborting model. In an unwinding model, the transaction's associated object's destructor will explicitly roll back. In a world where |
@SiegeLord Thanks for the feedback. To clarify, the difficulty isn't in defining what a function's contract is -- as you say, the documentation for a function can and should detail its preconditions and postconditions. The difficulty is rather in giving clear guidelines for choosing contracts in API design. When should you use a precondition (and fail on violation) versus not having a precondition, and instead passing on the result of input checking via For example, If When I wrote the current guidelines, I tried to give crisp criteria for choosing between preconditions versus returning A couple of other points:
|
My example was illustrating that these guidelines if followed to the letter will lead to complicated caller code that is not amenable to syntax sugar. The point is that if I encode the contract violation into the let tex = match create_texture(x, y) {
Ok(tex) => tex,
Err(ContractError) => fail!("{}", ContractError);
Err(MemoryError) => return Err(MemoryError);
}; while if the contract failure was put inside the function, the code could be simplified: let tex = try!(create_texture(x, y)); or, to get more fancy: let tex = try!(create_texture(x, y).map_err(|_| create_texture(x / 2, y / 2)); It's not just 'add one more line to the match statement', it's actually 'opt-out of the macro/API sugar associated with I disagree the that debugability is not an issue with the proposed guidelines. The complete call stack is a super-set of what you obtain by cutting it off at the place where you supply the inputs. Unless the
I think creating crisp guidelines that resolve any API design questions is a pipe dream. The goal of guidelines should be to write robust, bug-free software using clean-looking code. These new guidelines seem to encourage hiding bugs by makng the robust code look unclean. I recently switched some of my code to use callee-failure for obvious bugs, and I found my code to only benefit from it (as far as I can tell, at least). |
6357402
to
e0acdf4
Compare
@SiegeLord Thanks for the clarification, and sorry for the delay in getting back to you. I think you've raised a significant drawback for the proposal, and agree that clean and robust client-side code trumps having a rigid guideline. I'm going to close this RFC in favor of a new one I've just opened. I think the guidelines in the new RFC are pretty well-aligned with what you proposed in an earlier comment. |
fix some typos
This RFC sets out the first part of a vision for refining error handling in
Rust, by proposing a more clear-cut set of conventions around
fail!
andResult
. A separate follow-up RFC will propose syntactic sugar to make theproposed convention more ergonomic.
In a nutshell, the proposal is to isolate uses of
fail!
to an extremely smallset of well-known methods (and assertion violations), with all other kinds of
errors going through
Option
/Result
for maximal flexibility.Rendered