-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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: handling partial functions in Rust libraries #1945
Comments
I'd differentiate cases here.
The basis of this preference is, in a word, "frequency". Datatypes involved in the first set are pervasive (numbers, containers), have a lot of operations, and most operations fail on few and sparsely-distributed cases (0, empty vector, out-of-range index) relative to the dense set of successful cases. IOW these are cases most easily understood as exceptions-to-a-rule. Currently we model exceptions-to-rules (in terms of proportion) as failures, so I'd carry on with that. I understand that frequency is not always easy to judge, and sometimes you'll want "both interfaces" (hashmaps I think usually have a failing and option-returning variant). When frequency is hard to judge, provision of one or the other, or both, becomes more a matter of taste. I think it's generally ok to support both if a user asks for both, in which case we're faced with an even smaller question: what to name them when providing both. For this I think the one that returns non-option should be the shorter of the two names -- that is, provide Aside from all that, I also want to reiterate, when The design is sketched here: https://mail.mozilla.org/pipermail/rust-dev/2011-November/000999.html It's important to understand that this design works quite differently from exceptions. Handlers are dynamically scoped as in exceptions, but It never unwinds unless it's failing, and failure remains idempotent. Non-failure does not unwind, just recovers at the signal site. |
Would it be possible or even sensible for Rust to have return-type polymorphism? |
When i read "very-rare-frequency exceptional cases" i understand this as "such problems will almost never occur, we should not bother too much". Do i misread you here? I wonder because similar complains regarding functions like |
Yes, you are misreading. I'm not saying they're no big deal; I'm saying that the mechanism of "returning an option" for functions with very-rare failure modes is sufficiently unpleasant that we have more language-engineering to do to solve it. I think using options, like using strings, booleans or integers, is a matter of taste; having the flexible and useful hammer of sum types tempts us to view any "finite set of alternatives" problem as a potential nail. I don't think returning option types for all failure cases (and combining them together when there are multiple forms of failure, a la multiple nested exception monads) is always the right approach. It's too awkward for casual users on interfaces that rarely fail. We need a different, more subtle technique for those cases. I have suggested a couple. I'm open to others. |
I don't see clear consensus on this and it seems like we have plenty to do for 1.0, so I suggest we postpone this discussion. |
Closing since @graydon 's condition system is now checked in. |
I don't have a specific proposal, but I do want to discuss principles for how to handle partial functions in the standard libraries. Examples of partial functions are
option::get
(undefined onnone
),vec::last
(undefined on empty vectors), andmap::get
(undefined for keys not bound in the map).One approach is to "totalize" these functions by lifting their results into the
option
type: for example,vec::last
would returnnone
if invoked on an empty vector andsome(x)
on a non-empty vector, for somex
. (That was the behavior until today.)Another approach is to use typestate to specify preconditions so that we write total functions on a smaller domain, rather than partial functions on a bigger domain.
Finally, perhaps the simplest approach is for library functions to
fail
with an uncaught exception when error conditions arise.I prefer either the first or second approach over the third (ideally I would prefer the second, but clearly there has been some hesitance to use typestate in anger). My preference is informed by my experience with Haskell where the use of functions like
fromJust
orhead
in cases where the invariants don't really hold (and the compiler doesn't help by checking those invariants) is a major pain. If you know an invariant, you should be able to write it down and express it either through types or typestate (whether it's types or typestate is more of an implementation detail to me).However, I just changed
vec::last
to fail on an empty vector, for consistency with some of the existingvec
code. I'd like to arrive at some guiding principles here.The text was updated successfully, but these errors were encountered: