-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Provide a common API across Option
and the Ok
and Err
variants of Result
.
#113
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
Closed
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
69148fc
Provide a common API across `Option` and the `Ok` and `Err` variants …
brendanzab 1c56736
Restore old iterator methods for Option and update Result API accordi…
brendanzab 9a387a6
Restore slice conversion methods and update Result API
brendanzab 2a63bf4
Fix some impossible method signatures
brendanzab 460afea
Remove Expect trait
brendanzab a87e12a
Bring Result::unwrap and ForErr::unwrap type signatures into line wit…
brendanzab 2ebca82
Rename Result::for_err to Result::err
brendanzab File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
- Start Date: 2014-06-09 | ||
- RFC PR #: (leave this empty) | ||
- Rust Issue #: (leave this empty) | ||
|
||
# Summary | ||
|
||
Provide a common API across `Option` and the `Ok` and `Err` variants of | ||
`Result`. | ||
|
||
# Motivation | ||
|
||
The current `Option` API has a comprehensive set of methods to make dealing | ||
with the wrapped values more convenient: | ||
|
||
~~~rust | ||
pub enum Option<T> { None, Some(T) } | ||
|
||
impl<T> Option<T> { | ||
pub fn is_some(&self) -> bool { ... } | ||
pub fn is_none(&self) -> bool { ... } | ||
pub fn as_ref<'r>(&'r self) -> Option<&'r T> { ... } | ||
pub fn as_mut<'r>(&'r mut self) -> Option<&'r mut T> { ... } | ||
pub fn as_slice<'r>(&'r self) -> &'r [T] { ... } | ||
pub fn as_mut_slice<'r>(&'r mut self) -> &'r mut [T] { ... } | ||
pub fn unwrap(self) -> T { ... } | ||
pub fn unwrap_or(self, def: T) -> T { ... } | ||
pub fn unwrap_or_else(self, f: || -> T) -> T { ... } | ||
pub fn expect(self, msg: &str) -> T { ... } | ||
pub fn map<U>(self, f: |T| -> U) -> Option<U> { ... } | ||
pub fn map_or<U>(self, def: U, f: |T| -> U) -> U { ... } | ||
pub fn mutate(&mut self, f: |T| -> T) -> bool { ... } | ||
pub fn mutate_or_set(&mut self, def: T, f: |T| -> T) -> bool { ... } | ||
pub fn iter<'r>(&'r self) -> Item<&'r T> { ... } | ||
pub fn mut_iter<'r>(&'r mut self) -> Item<&'r mut T> { ... } | ||
pub fn move_iter(self) -> Item<T> { ... } | ||
pub fn and<U>(self, optb: Option<U>) -> Option<U> { ... } | ||
pub fn and_then<U>(self, f: |T| -> Option<U>) -> Option<U> { ... } | ||
pub fn or(self, optb: Option<T>) -> Option<T> { ... } | ||
pub fn or_else(self, f: || -> Option<T>) -> Option<T> { ... } | ||
pub fn take(&mut self) -> Option<T> { ... } | ||
pub fn filtered(self, f: |t: &T| -> bool) -> Option<T> { ... } | ||
pub fn while_some(self, f: |v: T| -> Option<T>) { ... } | ||
pub fn take_unwrap(&mut self) -> T { ... } | ||
pub fn get_ref<'a>(&'a self) -> &'a T { ... } | ||
pub fn get_mut_ref<'a>(&'a mut self) -> &'a mut T { ... } | ||
} | ||
|
||
impl<T: Default> Option<T> | ||
pub fn unwrap_or_default(self) -> T { ... } | ||
} | ||
~~~ | ||
|
||
The `Result` API provides a similar, yet greatly reduced set of methods that | ||
are biased either towards the `Ok` variant or the `Err` variant (denoted with | ||
the `_err` suffix): | ||
|
||
~~~rust | ||
// core::result | ||
|
||
pub enum Result<T, E> { Ok(T), Err(E) } | ||
|
||
impl<T, E> Result<T, E> { | ||
pub fn is_ok(&self) -> bool { ... } | ||
pub fn is_err(&self) -> bool { ... } | ||
pub fn ok(self) -> Option<T> { ... } | ||
pub fn err(self) -> Option<E> { ... } | ||
pub fn as_ref<'r>(&'r self) -> Result<&'r T, &'r E> { ... } | ||
pub fn as_mut<'r>(&'r mut self) -> Result<&'r mut T, &'r mut E> { ... } | ||
pub fn map<U>(self, op: |T| -> U) -> Result<U,E> { ... } | ||
pub fn map_err<F>(self, op: |E| -> F) -> Result<T,F> { ... } | ||
pub fn and<U>(self, res: Result<U, E>) -> Result<U, E> { ... } | ||
pub fn and_then<U>(self, op: |T| -> Result<U, E>) -> Result<U, E> { ... } | ||
pub fn or(self, res: Result<T, E>) -> Result<T, E> { ... } | ||
pub fn or_else<F>(self, op: |E| -> Result<T, F>) -> Result<T, F> { ... } | ||
pub fn unwrap_or(self, optb: T) -> T { ... } | ||
pub fn unwrap_or_else(self, op: |E| -> T) -> T { ... } | ||
} | ||
|
||
impl<T, E: Show> Result<T, E> { | ||
pub fn unwrap(self) -> T { ... } | ||
} | ||
|
||
impl<T: Show, E> Result<T, E> { | ||
pub fn unwrap_err(self) -> E { ... } | ||
} | ||
~~~ | ||
|
||
Which methods that are and are not implemented on `Result` does not seem to be | ||
well thought through. This can make it hard to transition code from `Option` | ||
to `Result` and make it irritating for the client when a standard operation | ||
that is implemented on `Option` is not available when working with `Result` | ||
values. A `Result` *can* be converted to an `Option` using the | ||
`Result::{ok, err}` methods, but this is a lossy transformation, losing the | ||
value stored either in the `Err` or `Ok` variant respectively. | ||
|
||
The inconsistency will also make it more challenging to transition to an API | ||
with a single trait that abstracts over Option-style things once higher-kinded | ||
types are implemented post-1.0. | ||
|
||
# Detailed design | ||
|
||
The goal is to provide a common set of methods across `Option`, and both the | ||
`Ok` and `Err` variants of `Result`. Ideally we would do this by defining a | ||
trait but without higher-kinded types we cannot define such a trait. As an | ||
interim solution we will have separate, matching `impl` blocks in `Option` and | ||
`Result` which can eventually be made into trait implementations once | ||
higher-kinded traits are definable. A `ForErr` adapter struct will also be | ||
added to provide method implementations biased towards the `Err` variant. | ||
|
||
## Option API | ||
|
||
The `Option` API will be cleaned up, and the methods that are shared with | ||
`Result` will be clearly differentiated in the source code: | ||
|
||
~~~rust | ||
// core::option | ||
|
||
pub enum Option<T> { None, Some(T) } | ||
|
||
// Option-specific predicates | ||
impl<T> Option<T> { | ||
pub fn is_some(&self) -> bool { ... } | ||
pub fn is_none(&self) -> bool { ... } | ||
} | ||
|
||
// Reference conversion methods | ||
impl<T> Option<T> { | ||
pub fn as_ref<'r>(&'r self) -> Option<&'r T> { ... } | ||
pub fn as_mut<'r>(&'r mut self) -> Option<&'r mut T> { ... } | ||
} | ||
|
||
// Methods in common with Result | ||
impl<T> Option<T> { | ||
pub fn as_slice<'r>(&'r self) -> &'r [T] { ... } | ||
pub fn as_mut_slice<'r>(&'r mut self) -> &'r mut [T] { ... } | ||
pub fn unwrap(self) -> T { ... } | ||
pub fn unwrap_or(self, def: T) -> T { ... } | ||
pub fn unwrap_or_else(self, f: || -> T) -> T { ... } | ||
pub fn expect(self, msg: &str) -> T { ... } | ||
pub fn map<U>(self, f: |T| -> U) -> Option<U> { ... } | ||
pub fn map_or<U>(self, def: U, f: |T| -> U) -> U { ... } | ||
pub fn mutate(&mut self, f: |T| -> T) -> bool { ... } | ||
pub fn mutate_or_set(&mut self, def: T, f: |T| -> T) -> bool { ... } | ||
pub fn iter<'r>(&'r self) -> SomeItem<&'r T> { ... } | ||
pub fn mut_iter<'r>(&'r mut self) -> SomeItem<&'r mut T> { ... } | ||
pub fn move_iter(self) -> SomeItem<T> { ... } | ||
pub fn and<U>(self, optb: Option<U>) -> Option<U> { ... } | ||
pub fn and_then<U>(self, f: |T| -> Option<U>) -> Option<U> { ... } | ||
pub fn or(self, optb: Option<T>) -> Option<T> { ... } | ||
pub fn or_else(self, f: || -> Option<T>) -> Option<T> { ... } | ||
} | ||
|
||
// Option-specific methods | ||
impl<T> Option<T> { | ||
pub fn take(&mut self) -> Option<T> { ... } | ||
pub fn filtered(self, f: |t: &T| -> bool) -> Option<T> { ... } | ||
pub fn while_some(self, f: |v: T| -> Option<T>) { ... } | ||
} | ||
|
||
impl<T: Default> Option<T> | ||
pub fn unwrap_or_default(self) -> T { ... } | ||
} | ||
~~~ | ||
|
||
### Changes from the old Option API | ||
|
||
Old API | New API | ||
--------------------|-------------------------------------------------- | ||
`.get_ref()` | `.as_ref().unwrap()` | ||
`.get_mut_ref()` | `.as_mut().unwrap()` | ||
`.take_unwrap()` | `take().unwrap()` | ||
|
||
## Result API | ||
|
||
Even though we can't express the abstraction over Option-like things without | ||
higher kinded types, we should aim to make the methods implemented on `Result` | ||
consistent with the `Option` API. | ||
|
||
When implemented directly on `Result`, these methods would be biased towards | ||
the `Ok` variant. The `_err`-suffixed methods would be removed, replaced by a | ||
`ForErr` adapter type that would allow for the Option-style methods to be | ||
implemented again - this time being biased towards the `Err` variant. | ||
|
||
The `take`, `filtered` and `while_some` methods from `Option` are not included | ||
in the `Result` API because they require a default `None` value. | ||
|
||
~~~rust | ||
// core::result | ||
|
||
use std::any::Any { ... } | ||
|
||
pub enum Result<T, E> { Ok(T), Err(E) } | ||
|
||
// Result-specific predicates | ||
impl<T, E> Result<T, E> { | ||
pub fn is_ok(&self) -> bool { ... } | ||
pub fn is_err(&self) -> bool { ... } | ||
} | ||
|
||
// Reference conversion methods | ||
impl<T, E> Result<T, E> { | ||
pub fn as_ref<'a>(&'a self) -> Result<&'a T, &'a E> { ... } | ||
pub fn as_mut<'a>(&'a mut self) -> Result<&'a mut T, &'a E> { ... } | ||
} | ||
|
||
// Conversion methods | ||
impl<T, E> Result<T, E> { | ||
pub fn to_option(self) -> Option<T> { ... } | ||
pub fn err(self) -> ForErr<T, E> { ... } | ||
} | ||
|
||
// Methods in common with Option | ||
impl<T, E> Result<T, E> { | ||
pub fn as_slice<'r>(&'r self) -> &'r [T] { ... } | ||
pub fn as_mut_slice<'r>(&'r mut self) -> &'r mut [T] { ... } | ||
pub fn unwrap_or(self, def: T) -> T { ... } | ||
pub fn unwrap_or_else(self, f: || -> T) -> T { ... } | ||
pub fn expect(self, msg: &str) -> T { ... } | ||
pub fn map<U>(self, f: |T| -> U) -> Result<U, E> { ... } | ||
pub fn map_or<U>(self, def: U, f: |T| -> U) -> U { ... } | ||
pub fn mutate(&mut self, f: |T| -> T) -> bool { ... } | ||
pub fn mutate_or_set(&mut self, def: T, f: |T| -> T) -> bool { ... } | ||
pub fn iter<'r>(&'r self) -> OkItem<&'r T> { ... } | ||
pub fn mut_iter<'r>(&'r mut self) -> OkItem<&'r mut T> { ... } | ||
pub fn move_iter(self) -> OkItem<T> { ... } | ||
pub fn and<U>(self, other: Result<U, E>) -> Result<U, E> { ... } | ||
pub fn and_then<U>(self, f: |T| -> Result<U, E>) -> Result<U, E> { ... } | ||
pub fn or(self, other: Result<T, E>) -> Result<T, E> { ... } | ||
pub fn or_else(self, f: || -> Result<T, E>) -> Result<T, E> { ... } | ||
} | ||
|
||
impl<T, E: Show> Result<T, E> { | ||
pub fn unwrap(self) -> T { ... } | ||
} | ||
|
||
pub struct ForErr<T, E>(pub Result<T, E>); | ||
|
||
// Conversion methods | ||
impl<T, E> ForErr<T, E> { | ||
pub fn to_result(self) -> Result<T, E> { ... } | ||
pub fn to_option(self) -> Option<E> { ... } | ||
} | ||
|
||
// Methods in common with Option | ||
impl<T, E> ForErr<T, E> { | ||
pub fn as_slice<'r>(&'r self) -> &'r [E] { ... } | ||
pub fn as_mut_slice<'r>(&'r mut self) -> &'r mut [E] { ... } | ||
pub fn unwrap(self) -> E { ... } | ||
pub fn unwrap_or(self, def: E) -> E { ... } | ||
pub fn unwrap_or_else(self, f: || -> E) -> E { ... } | ||
pub fn expect(self, msg: &str) -> E { ... } | ||
pub fn map<F>(self, f: |E| -> F) -> Result<T, F> { ... } | ||
pub fn map_or<F>(self, def: F, f: |E| -> F) -> F { ... } | ||
pub fn mutate(&mut self, f: |E| -> E) -> bool { ... } | ||
pub fn mutate_or_set(&mut self, def: E, f: |E| -> E) -> bool { ... } | ||
pub fn iter<'r>(&'r self) -> ErrItem<&'r E> { ... } | ||
pub fn mut_iter<'r>(&'r mut self) -> ErrItem<&'r mut E> { ... } | ||
pub fn move_iter(self) -> ErrItem<E> { ... } | ||
pub fn and<F>(self, other: Result<T, F>) -> Result<T, F> { ... } | ||
pub fn and_then<F>(self, f: |E| -> Result<T, F>) -> Result<T, F> { ... } | ||
pub fn or(self, other: Result<T, E>) -> Result<T, E> { ... } | ||
pub fn or_else(self, f: || -> Result<T, E>) -> Result<T, E> { ... } | ||
} | ||
|
||
impl<T: Show, E> ForErr<T, E> { | ||
pub fn unwrap(self) -> E { ... } | ||
} | ||
~~~ | ||
|
||
### Added methods | ||
|
||
- `Result::err` | ||
- `Result::unwrap` | ||
- `Result::expect` | ||
- `Result::map_or` | ||
- `Result::mutate` | ||
- `Result::mutate_or_set` | ||
- `Result::iter` | ||
- `ForErr::unwrap` | ||
- `ForErr::unwrap_or` | ||
- `ForErr::unwrap_or_else` | ||
- `ForErr::expect` | ||
- `ForErr::map_or` | ||
- `ForErr::mutate` | ||
- `ForErr::mutate_or_set` | ||
- `ForErr::iter` | ||
- `ForErr::mut_iter` | ||
- `ForErr::move_iter` | ||
- `ForErr::and` | ||
- `ForErr::and_then` | ||
- `ForErr::or` | ||
- `ForErr::or_else` | ||
|
||
### Changes from the old Result API | ||
|
||
Old API | New API | ||
--------------------|-------------------------------------------------- | ||
`.ok()` | `.to_option()` | ||
`.err()` | `.err().to_option()` | ||
`.map_err(...)` | `.err().map(...)` | ||
`.or_else(...)` | `.err().or(...)` | ||
|
||
# Drawbacks | ||
|
||
Working with `Err` values is more verbose. It also increases the complexity of | ||
the `Result` API. | ||
|
||
# Alternatives | ||
|
||
What other designs have been considered? What is the impact of not doing this? | ||
|
||
- `Result::err` could be renamed to `Result::for_err`. This would be more | ||
in line with the resulting type name, but would also be more verbose. | ||
- Instead of using the `ForErr` adapter, we could instead just use the `_err` | ||
suffix. This would be slightly more convenient, but would incompatible | ||
with a move to a trait-based API in the future. | ||
- Instead of implementing the `Ok`-biased methods directly on `Result`, a | ||
`ForOk` adapter and `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. | ||
- We could remove the `Err` methods entirely, because they bloat the API | ||
considerably. | ||
|
||
# Unresolved questions | ||
|
||
To preserve the current behaviour of `Result::{unwrap, unwrap_err}`, | ||
`Result::unwrap` and `ForErr::unwrap` must be implemented on `E: fmt::Show` | ||
and `T: fmt::Show` respectively. It is unclear how to make this completely | ||
mirror the `Option` API. | ||
|
||
It might be a good idea to think more deeply about how the API will be | ||
structured with higher-kinded types, dividing the `impl`s up in a more | ||
fine-grained way, annotating them with comments referring to their future | ||
traits. This would be similar to how the STL uses the idea of 'concepts', even | ||
though they are still not implemented in the language. This kind of thinking | ||
could also extend to the library defined pointer types, which could be tied | ||
together in the future with a higher kinded abstraction. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once you’ve got that, you’re back to the current situation with
Result.ok()
andResult.err()
, and so the actual effect of this is simply “remove all the methods onResult
except forok
anderr
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Treating
Result
as a kind ofOption
with optional error information is nice.