Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upProvide a common API across `Option` and the `Ok` and `Err` variants of `Result`. #113
Conversation
This comment has been minimized.
This comment has been minimized.
|
I'm sad that this appears to make error handling uniformly more verbose.
What would this actually allow us to do, in practical terms? Feel free to invent syntax as needed. |
huonw
reviewed
Jun 10, 2014
|
|
||
| Old API | New API | ||
| --------------------|-------------------------------------------------- | ||
| `.as_slice()` | <code>.as_ref().map_or(&[], |x| slice::ref_slice(x))</code> |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
brendanzab
Jun 10, 2014
Author
Member
Yeah, I had to use those because the pipes were interacting with the markdown table syntax.
This comment has been minimized.
This comment has been minimized.
thehydroimpulse
commented
Jun 10, 2014
Only a single trait will be required to implement the common methods. Separate implementations will be used for // Methods in common with Result and Option.
trait Optional<M[T]> {
pub fn unwrap_or(self, def: T) -> T { ... }
pub fn unwrap_or_else(self, f: || -> T) -> T { ... }
}
impl<M[T]> Optional<M[T]> for Option<T> {
// ...
}
// ...The So in short, we're allowed to abstract over more complex types. |
This comment has been minimized.
This comment has been minimized.
|
I was mostly curious as to how this would make the usage of Option nicer, rather than its implementation. I really couldn't care less about the implementation, as long as it's as nice to use as we can make it. |
This comment has been minimized.
This comment has been minimized.
thehydroimpulse
commented
Jun 10, 2014
|
Gotcha. |
chris-morgan
reviewed
Jun 10, 2014
| What other designs have been considered? What is the impact of not doing this? | ||
|
|
||
| - `Result::for_err` could be renamed to `Result::err`. This would be more | ||
| succinct. |
This comment has been minimized.
This comment has been minimized.
chris-morgan
reviewed
Jun 10, 2014
| - Instead of implementing the `Ok`-biased methods directly on `Result`, a | ||
| `ForOk` adapter and `for_ok` method could be added, mirroring `ForErr`. This | ||
| would be more inconvenient for the common case, but would be more | ||
| symmetrical with the `Err` biased API. |
This comment has been minimized.
This comment has been minimized.
chris-morgan
Jun 10, 2014
Member
Once you’ve got that, you’re back to the current situation with Result.ok() and Result.err(), and so the actual effect of this is simply “remove all the methods on Result except for ok and err.
This comment has been minimized.
This comment has been minimized.
brendanzab
Jun 10, 2014
Author
Member
Indeed, I am not really a fan of this. I think implementing the most common case directly on Result is the right way to go.
This comment has been minimized.
This comment has been minimized.
wycats
Jun 21, 2014
Contributor
Agreed. Treating Result as a kind of Option with optional error information is nice.
This comment has been minimized.
This comment has been minimized.
Valloric
commented
Jun 10, 2014
This. The user doesn't care how nicely refactored the compiler internals are. |
This comment has been minimized.
This comment has been minimized.
That is the point. To simplify things by providing a set of standard methods. You only have to remember one set of methods rather than two, inconsistent APIs. |
This comment has been minimized.
This comment has been minimized.
|
I apologize if I seem grumpy. My complaints stem from the fact that I've never believed that our Option handling is as nice as it needs to be, given how fundamental to the language that it is. Other languages with Options all seem to have mechanisms to ease usage (Haskell, Swift, C#). I fear that people familiar with these languages will judge us unfavorably if idiomatic Option handling is overly clunky. |
This comment has been minimized.
This comment has been minimized.
schmee
commented
Jun 10, 2014
|
Why not introduce some sugar for |
This comment has been minimized.
This comment has been minimized.
sinistersnare
commented
Jun 10, 2014
|
As I understand it, is the reason that we do not give special sugar for |
This comment has been minimized.
This comment has been minimized.
thehydroimpulse
commented
Jun 10, 2014
I agree. In this situation, it would provide a much simpler implementation and a much nicer interface for users. That's with the introduction of monads and potentially re-introducing the Here's an example using a fictitious do! {
action1();
action2();
action3();
}
fn action1() -> Option<int> {
return Some(5);
}
fn action2() -> Option<int> {
return Some(55);
}
fn action3() -> Option<String> {
return "foobar".to_string();
}Moreover, one could bind the value of do! {
let a <- action1();
let b <- action2();
action3(a, b);
}
fn action3(a: int, b: int) -> Option<String> {
return "".to_string();
}The reason for having This would ideally all be done through macros, so as to not give special treatment to the few types. I find the HKT and monad abstraction to be much more appropriate than simple sugar like (You would still need to handle a fail case, otherwise it might fail for you) |
This comment has been minimized.
This comment has been minimized.
|
Is there a reason that we can't implement the |
This comment has been minimized.
This comment has been minimized.
thehydroimpulse
commented
Jun 10, 2014
|
I haven't put much thought about how this would all affect So one could do: fn something_important() -> Option<String> {
return do! {
let a <- action1();
let b <- action2();
action3(a, b);
};
}Versus: fn something_important() -> Option<String> {
match (action1(), action2()) {
(Some(a), Some(b)) => {
match action3(a, b) {
Some(s) => Some(s),
None => return None
}
},
_ => return None
}
}So it would result in composition similar to |
This comment has been minimized.
This comment has been minimized.
|
I think it's worth separating out two aspects of this RFC:
It's hard to argue against the first point: it seems pretty clear that the overall API will be improved if we make the two types support a common interface. Is there any disagreement about that? @bjz, perhaps it's worth outlining an additional alternative for your design that addresses point 1 above but not point 2, i.e. keeps more of the convenience methods currently available for Here are my thoughts on point 2:
|
This comment has been minimized.
This comment has been minimized.
|
@aturon Good points. Re. |
This comment has been minimized.
This comment has been minimized.
|
@bjz I don't have any data here, but my feeling is: they are simple and easy to understand, especially given the |
brendanzab
added some commits
Jun 10, 2014
This comment has been minimized.
This comment has been minimized.
|
Ok, I restored the old iterator and slice conversion methods and mirrored them for |
This comment has been minimized.
This comment has been minimized.
pczarn
commented
Jun 11, 2014
|
I prefer the There's a way to change the type without discarding the other value.
Additionally, old methods
* Since |
This comment has been minimized.
This comment has been minimized.
|
So are you saying that you would have two phantom types, |
This comment has been minimized.
This comment has been minimized.
pczarn
commented
Jun 12, 2014
|
Yes, either those enums or |
This comment has been minimized.
This comment has been minimized.
|
They would have to be phantom types if they were going to reuse the same identifiers as the |
This comment has been minimized.
This comment has been minimized.
|
I'm in favor of this. Seems like a clean refactor to a consistent API. Consistency is king. |
This comment has been minimized.
This comment has been minimized.
|
@cmr Thoughts on the phantom type idea? |
This comment has been minimized.
This comment has been minimized.
|
The complexity does not pay for itself, I think. |
This comment has been minimized.
This comment has been minimized.
|
@pczarn Can you elaborate on what you see as the benefits of using phantom types here? It looks like Is your proposal mainly about the aesthetics of the types (just one type with a parameter versus two types), or does it make client code nicer/easier in some way? |
This comment has been minimized.
This comment has been minimized.
|
I'm fine with this. Consistency is good. And if people decide that they need even more convenience methods in the future, we just need to be sure that they can be implemented sanely for both types. |
This comment has been minimized.
This comment has been minimized.
|
I am in favor of this RFC, modulo one concern: I don't understand why the Also, minor note: the |
This comment has been minimized.
This comment has been minimized.
|
The |
This comment has been minimized.
This comment has been minimized.
|
Oh wait, |
This comment has been minimized.
This comment has been minimized.
|
@aturon: re. |
This comment has been minimized.
This comment has been minimized.
|
Oh, it seems that the |
This comment has been minimized.
This comment has been minimized.
|
@bjz I think it's fine to leave I wonder whether Also, |
This comment has been minimized.
This comment has been minimized.
|
@bjz Sorry, just noticed you suggested |
This comment has been minimized.
This comment has been minimized.
|
We talked about this in person yesterday, but I wanted to write this down for posterity: This proposal means that foo.map_err(|e| /* ... */)would become foo.for_err().map(|e| /* ... */).to_result()which I sadly find quite wordy :( |
This comment has been minimized.
This comment has been minimized.
|
Or rather, you considered it a mild bug that |
This comment has been minimized.
This comment has been minimized.
|
Thanks for writing these things down - I will update the PR accordingly today. |
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton FWIW, I think it's OK for the foo.err().map(|e| /* ... */)rather than today's foo.map_err(|e| /* ... */)which is not too bad. @bjz I realized this morning that the semantics of methods like |
brendanzab
added some commits
Jun 23, 2014
This comment has been minimized.
This comment has been minimized.
|
@aturon re. the semantics of impl<T, E> Result<T, E> {
pub fn and<U>(self, other: Result<U, E>) -> Result<U, E> {
match self {
Ok(_) => other,
Err(e) => Err(e),
}
}
pub fn or(self, other: Result<T, E>) -> Result<T, E> {
match self {
Ok(x) => Ok(x),
Err(_) => other,
}
}
}
impl<T, E> ForErr<T, E> {
pub fn and<F>(self, other: Result<T, F>) -> Result<T, F> {
match self {
ForErr(Err(_)) => other,
ForErr(Ok(x)) => Ok(x),
}
}
pub fn or(self, other: Result<T, E>) -> Result<T, E> {
match self {
ForErr(Err(e)) => Err(e),
ForErr(Ok(_)) => other,
}
}
} |
This comment has been minimized.
This comment has been minimized.
|
@bjz Thanks for the elaboration. I have mixed feelings about these methods. The definitions you give here are somewhat symmetric to those in Would it make sense to have them take Perhaps related: if you did eventually want to write a trait encompassing the |
This comment has been minimized.
This comment has been minimized.
|
Yeah, as we figured out the other day, for them to satisfy a truly common API the |
This comment has been minimized.
This comment has been minimized.
|
@bjz Right -- I wanted to get that in the public record :-) But a broader point is, if we're not actually making |
This comment has been minimized.
This comment has been minimized.
|
No reason - I think it was a brain-fart on my part :) |
This comment has been minimized.
This comment has been minimized.
bluss
commented
Jun 27, 2014
|
The proposal seems to add net complexity rather than remove it. I think -- my perspective is simpler API, simpler usage -- this proposal should be abandoned, take a step back, look at smaller things that can be adjusted in Result and Option -- for example removing |
This comment has been minimized.
This comment has been minimized.
nielsle
commented
Jul 11, 2014
|
Slightly orthogonal: It would be nice to have a simple method for converting an Option to a Result<T,E>. pub fn ok_or<E>(self, f: || -> E) -> Result<T,E> { ... }That would allow let x: u32 = try!(from_str("25").ok_or(|| MyConversionError)) |
This comment has been minimized.
This comment has been minimized.
nielsle
commented on active/0000-option-result-api.md in 69148fc
Jul 12, 2014
|
Perhaps E should be required to implement Show. impl<T, E: Show> Expect<T> for Result<T, E> {That would allow rust to print the error message when failing to unwrap, and hopefully that would help newbies dealing with Results in See also a relevant discussion of a commit to "Guide: Standard input": |
This comment has been minimized.
This comment has been minimized.
|
Ok, @aturon and I are thinking that adding a |
This comment has been minimized.
This comment has been minimized.
|
@bjz With the |
This comment has been minimized.
This comment has been minimized.
pczarn
commented
Aug 28, 2014
|
I think the phantom type idea would easily work with HKT. That's the only benefit I see. (I'm surprised this RFC is still open) |
This comment has been minimized.
This comment has been minimized.
|
@aturon Ok, I'll close this now then! |
brendanzab commentedJun 9, 2014
Rendered