# okerrr!
**Keep the `Ok`. Case the `Err`. Carry on.**
`okerrr!` is a dependency-free, `no_std` macro that extracts a `Result::Ok`
payload and dispatches its raw error through exhaustive, diverging `case`
clauses.
[](https://crates.io/crates/okerrr)
[](https://docs.rs/okerrr)
[](https://github.com/awill1988/okerrr/actions/workflows/ci.yml)
[](https://codecov.io/gh/awill1988/okerrr)
[](LICENSE-MIT)
```rust
use okerrr::okerrr;
fn process(input: Result<i32, &'static str>) -> Result<i32, &'static str> {
let value = okerrr!(input, case error => return Err(error));
Ok(value * 2)
}
```
The expression runs once. An `Ok` becomes the value of the macro. Each `case`
matches the raw `Err` payload and must leave the current path through `return`,
`break`, `continue`, panic, or another never-returning expression.
## The repeated pattern
Rust's [`let-else`](https://rust-lang.github.io/rfcs/3137-let-else.html)
keeps the successful value in the surrounding scope and requires the failure
branch to diverge. With a `Result`, however, its catch-all `else` pattern
cannot also bind the moved `Err` payload.
A `match` can bind that error and return from the caller:
```rust
fn process(input: Result<i32, &'static str>) -> Result<i32, &'static str> {
let value = match input {
Ok(value) => value,
Err(error) => return Err(error),
};
Ok(value * 2)
}
```
`okerrr!` keeps the same control flow while removing the repeated `match`,
`Ok`, and `Err` structure:
```rust
use okerrr::okerrr;
fn process(input: Result<i32, &'static str>) -> Result<i32, &'static str> {
let value = okerrr!(input, case error => return Err(error));
Ok(value * 2)
}
```
This is the narrow gap between `let-else` and `match`: keep the successful
binding in the surrounding scope while still inspecting the rejected payload.
## Case clauses
The macro has one form with one or more exhaustive error cases:
```rust
okerrr!(
result_expression,
case error_pattern if optional_guard => diverging_handler,
case fallback_pattern => diverging_handler,
)
```
Cases match the raw error. Rust checks exhaustiveness, and every handler must
leave the current path: usually with `return`, or inside a loop with `continue`
to skip an item or `break` to finish the loop.
The `case` spelling nods to [Elixir](https://hexdocs.pm/elixir/case-cond-and-if.html#case);
patterns, guards, ownership, and borrowing remain Rust. Use `@` to keep the
whole error while matching its shape:
```rust
case FetchError::Busy => continue,
case error @ FetchError::Fatal => return Err(error),
```
Multiple cases distinguish retryable and terminal errors without restoring the
outer `match`:
```rust
use okerrr::okerrr;
enum FetchError {
Busy { attempt: usize },
Fatal,
}
fn first_value(
inputs: impl IntoIterator<Item = Result<i32, FetchError>>,
retries_left: usize,
) -> Result<Option<i32>, FetchError> {
for input in inputs {
let value = okerrr!(
input,
case FetchError::Busy { attempt } if attempt < retries_left => continue,
case error @ FetchError::Busy { .. } => return Err(error),
case error => return Err(error),
);
return Ok(Some(value));
}
Ok(None)
}
```
## Async expressions
Async needs no separate feature. Await the input expression where it is
produced:
```rust
use okerrr::okerrr;
async fn load() -> Result<i32, &'static str> {
let value = okerrr!(fetch_value().await, case error => return Err(error));
Ok(value)
}
async fn fetch_value() -> Result<i32, &'static str> {
Ok(42)
}
```
The macro does not await implicitly.
## Caller-side tracing
`okerrr!` does not log or require the error type to implement `Debug` or
`Display`. A caller can instrument the branch explicitly:
```rust
use okerrr::okerrr;
fn read(input: Result<i32, &'static str>) -> Result<i32, &'static str> {
let value = okerrr!(input, case error => {
tracing::error!(error = ?error, "read failed");
return Err(error);
});
Ok(value)
}
```
The `Debug` requirement in this example comes from the caller's `?error`
field. The application owns its subscriber and any tracing-to-OpenTelemetry
pipeline.
The crate has no dependencies, enables no default features, and supports
`#![no_std]` on Rust `1.56` and later.
## The name
The name describes the domain first: **`Ok` + `Err` + `R(esult)` =
`okerrr!`**. Each part contributes meaning to the whole.
Read aloud, the extended `r` also gives a light phonetic nod to *okurrr*, a
trilled “okay” associated with drag culture and widely popularized by Cardi B.
The [cultural reference](https://www.dictionary.com/culture/pop-culture/okurrr)
sets the tone; the macro's contract comes from Rust.
## Contributor checks
Read [CONTRIBUTING.md](CONTRIBUTING.md) before proposing a change. Community
pull requests require an acknowledged issue before implementation begins.
The conventional commit linter is written in Rust. Activate the tracked hooks:
```sh
git config core.hooksPath .githooks
```
`pre-commit` checks formatting and strict Clippy for the crate and its
downstream `no_std` fixture. `commit-msg` checks conventional commit format.
Both stop invalid commits. Run `cargo fmt --all` and
`cargo fmt --manifest-path tests/fixtures/downstream/Cargo.toml`, then restage
reviewed changes before committing.
Squash merges use the pull request title as the final commit message. Mark
breaking changes with `!` in that title, such as
`feat!: add error case dispatch`, so release notes retain the signal.
## Releases
Ordinary pull requests keep the version in `Cargo.toml` unchanged. Their
merges run CI and do not publish.
To stage a release, run the `Prepare Release` workflow with an exact Cargo
version such as `0.1.0-rc.1`. The workflow validates the version against
`main` and crates.io, creates `release/v0.1.0-rc.1`, and opens a dedicated
release pull request. CI validates the package and attaches a release-notes
preview to that pull request.
Merging the release pull request publishes its exact manifest version to
crates.io and creates a matching GitHub release. Versions such as
`0.1.0-rc.1` become prerelease GitHub releases and are not marked latest.
Run `Prepare Release` with the next candidate or stable version to promote a
candidate. Release notes are generated in GitHub releases and include the
complete change set since the preceding stable version.
## License
Dual-licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE).