StdResult
is a library heavily inspired by Rust's Result
type for handling
function results in a consistent manner in Elixir.
Handling function results in Elixir can sometimes be inconsistent, with some
functions returning :ok
or :error
, while others return tuples like
{:ok, term}
or {:error, reason}
.
StdResult
provides macros and functions to create and manipulate results,
promoting a unified approach to error handling.
Detailed documentation can be found at https://hexdocs.pm/std_result.
Add std_result
to your list of dependencies in mix.exs:
def deps do
[
{:std_result, "~> 0.1"}
]
end
And that's all.
Let's take a simple example.
Let's say we need to retrieve an environment variable, convert it to an integer
and check that it's positive. Our function should return {:ok, value}
or
{:error, reason}
.
One of the most popular solutions is to use with
which would give something like:
with {:ok, port_str} <- System.fetch_env("PORT"),
port when port > 0 <- String.to_integer(port_str) do
{:ok, port}
else
# Return by System.fetch_env/1
:error -> {:error, "PORT env required"}
# Returned by `port when port > 0`
value when is_integer(value) -> {:error, "PORT must be a positive number, got: #{value}"}
end
I think you're beginning to understand what I'm getting at.
Here our error handling is very complicated to reread because the returns of the
functions used in the with
are not normalized.
This is a simple example, but the more conditions there are, the more confusing
the else
block becomes.
The simplest solution would be to wrap each function in another, normalizing the returns. But you'd soon find yourself with lots of functions that just normalize each other's returns.
StdResult
overcomes this problem.
Here is the same problem as above, but with StdResult
:
import StdResult
System.fetch_env("PORT")
# This will transform `:error` into a `:error` tuple
|> normalize_result()
# If there is an error, explicit the message
|> or_result(err("PORT env required"))
# If no error, parse the string as an integer
# We could also have used `Integer.parse/1` but for simplicity's sake we won't.
|> map(&String.to_integer/1)
# Test if the number is positive
|> and_then(&(if &1 >= 0, do: ok(&1), else: err("PORT must be a positive number, got: #{&1}")))
# The result will be either:
# - `{:ok, port}`
# - `{:error, "PORT env required"}`
# - `{:error, "PORT must be a positive number, got: <value>"}`
NOTE: This lib is not intended to replace with
, but rather to complement it in certain cases.
Contributions are welcome and appreciated. If you have any ideas, suggestions, or bugs to report, please open an issue or a pull request on GitHub.