https://github.com/mathiaspius/fallible-option
Fallible is an Option with inverted Try-semantics.
https://github.com/mathiaspius/fallible-option
Last synced: 10 months ago
JSON representation
Fallible is an Option with inverted Try-semantics.
- Host: GitHub
- URL: https://github.com/mathiaspius/fallible-option
- Owner: MathiasPius
- License: mit
- Created: 2023-01-10T14:22:12.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2023-01-12T14:11:39.000Z (over 3 years ago)
- Last Synced: 2025-08-18T08:29:49.502Z (10 months ago)
- Language: Rust
- Homepage:
- Size: 26.4 KB
- Stars: 26
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Fallible [![Latest Version]][crates.io] [![Docs]][docs.rs]
[Latest Version]: https://img.shields.io/crates/v/fallible-option
[crates.io]: https://crates.io/crates/fallible-option
[Docs]: https://docs.rs/fallible-option/badge.svg
[docs.rs]: https://docs.rs/fallible-option
[`Fallible`](https://docs.rs/fallible-option/latest/fallible_option/enum.Fallible.html) is an [`Option`](https://doc.rust-lang.org/stable/core/option/enum.Option.html) with inverted [`Try`](https://doc.rust-lang.org/stable/core/ops/trait.Try.html#)-semantics.
What this means is that using the `?` operator on a `Fallible` will exit early
if an error `E` is contained within, or instead act as a no-op, if the value is `Success`.
This is in contrast to `Option` where using `?` on a `None`-value will exit early.
`Fallible` fills the gap left by the [`Result`](https://doc.rust-lang.org/stable/core/result/enum.Result.html) and [`Option`](https://doc.rust-lang.org/stable/core/option/enum.Option.html) types:
| Potential Success | Potential Failure |
|---------------------|-------------------|
| `Result` |
| `Option` | **`Fallible`** |
### Example
This code illustrates how `Fallible` can be used to write succint
validation code which exits early in case of failure.
```rust
use fallible_option::Fallible::{self, Fail, Success};
// Validates the input number `n`, returning a `Fail`
// if the input number is zero, or `Success` otherwise.
fn fails_if_number_is_zero(n: u32) -> Fallible<&'static str> {
if n == 0 {
Fail("number is zero")
} else {
Success
}
};
// Check many numbers, returning early if a tested
// number is equal to zero.
fn check_many_numbers() -> Fallible<&'static str> {
fails_if_number_is_zero(1)?;
fails_if_number_is_zero(3)?;
fails_if_number_is_zero(0)?; // <--- Will cause early exit
// Following lines are never reached
fails_if_number_is_zero(10)?;
Success
}
assert_eq!(check_many_numbers(), Fallible::Fail("number is zero"));
```
### Motivation
`Fallible` fills the gap left by `Option` and `Result` and clearly conveys intent and potential outcomes of a function.
A function which returns `Fallible` has only two potential outcomes, it can fail with an error `E`, or it can succeed.
#### Why not `Result`?
Because `Result` implies output. Take `std::fs::rename` for instance:
If I told you that the return type of `rename` was a `Result`, what would you guess `T` and `E` to be?
You might rightly assume that `E` was `std::io::Error`, but what about `T`? It could reasonably return any number of things:
* The canonical path of the destination of the renamed file.
* The size of the moved file.
* The size of the file (if any) replaced by the renamed file.
* Or perhaps even a handle to the overwritten file.
Of course none of these are true, as the `T` value of `rename` is the unit value `()`. `rename` never
produces any output, it can only signal errors. So why not signal that clearly to the user?
I would argue that using a type which signals the potential for failure, but no output upon success would
more clearly express the intent and potential outcomes when using this function.
#### Why not `Option`?
Potential failure *could* be expressed using an `Option`, but as stated above, the `Try`-semantics
of `Option` makes it unergonomic to work with:
```rust
type Error = &'static str;
fn fails_if_number_is_zero(n: u32) -> Option {
if n == 0 {
Some("number is zero")
} else {
None
}
};
fn check_many_numbers() -> Option {
// We have to explicitly check, since using `?` here would result in an early exit,
// if the call returned None, which is the opposite of what we intend.
if let Some(err) = fails_if_number_is_zero(1) {
return Some(err)
}
// .. Repeating the above three lines for each check is tedious compared to
// just using the `?` operator, as in the example.
None
}
```
### Conversion from `Result`
Switching from using `Result` to `Fallible` is very simple, as illustrated with this before/after example:
```rust
fn validate_number(x: u32) -> Result<(), &'static str> {
match x {
0 ..= 9 => Err("number is too small"),
10..=30 => Ok(()),
31.. => Err("number is too large")
}
}
```
Using `Fallible`:
```rust
fn validate_number(x: u32) -> Fallible<&'static str> {
match x {
0 ..= 9 => Fail("number is too small"),
10..=30 => Success,
31.. => Fail("number is too large")
}
}
```
### Compatibility
`Fallible` contains utility functions for mapping to and from [`Result`] and [`Option`],
as well as [`FromResidual`] implementations for automatically performing these conversions
when used with the `?` operator.
```rust
fn fails_if_true(should_fail: bool) -> Fallible<&'static str> {
if should_fail {
Fail("Darn it!")
} else {
Success
}
}
fn try_producing_value() -> Result {
fails_if_true(false)?;
fails_if_true(true)?;
Ok(10)
}
```