Result
Error handling with the Result type.
Result<T, E>
is the type used for returning a possible error. It is a class with the
constructor variants, Result:Ok(T)
, representing success and containing a value, and
Result:Err(E)
, representing error and containing an error value.
local result: Result<number, string>
-- Both variants `Ok` and `Err` fit into the type
result = Ok(21)
result = Err("some error")
Functions return Result whenever errors are expected and recoverable.
A simple function returning Result might be defined and used like so:
local PAT = "v(%d+)"
function parseVersion(str: string): Result<number, string>
local version: number = string.match(str, PAT)
if version == 0 then
return Err("Version cannot be zero")
else if version == nil then
return Err("Invalid string")
end
return Ok(version)
end
print("Successful result: " .. parseVersion("v12"):display())
print("Error result: " .. parseVersion("vString"):display())
Result comes with some convenience methods that make working with it more succinct.
local goodResult: Result<number, number> = Ok(10)
local badResult: Result<number, number> = Err(10)
-- The `isOk`and `isErr` methods do what they say.
assert(goodResult:isOk() and not goodResult:isErr())
assert(badResult:isErr() and not badResult:isOk())
-- `map` produces a new result from an existing one
goodResult = goodResult:map(function(i) return i + 1 end)
badResult = badResult:map(function(i) return i - 1 end)
-- Use `and_then` to continue the computation.
local anotherGoodResult: Result<boolean, number> = goodResult:andThen(function(i) return Ok(i == 11) end)
-- Use `or_else` to handle the error
local badResult: Result<number, number> = bad_result:orElse(function(i) return Ok(i + 20) end)
-- Get the internal `Ok` value and panic if there is an `Err`
local finalAwesomeResult: boolean = anotherGoodResult:unwrap()
Functions
ok
Converts from Result<T, E>
to Option<T>
.
Converts self
into an Option<T>
, and discarding the error, if any.
local x: Result<number, string> = Ok(2)
assert(x:ok() == Some(2))
x = Err("Nothing here")
assert(x:ok() == None())
err
Converts from Result<T, E>
to Option<E>
.
Converts self
into an Option<E>
, and discarding the success value, if any.
local x: Result<number, string> = Ok(2)
assert(x:ok() == Some(2))
x = Err("Nothing here")
assert(x:ok() == None())
transpose
Transposes a Result of an Option into an Option of a Result.
Result:Ok(Option:None) will be mapped to Option:None.
Result:Ok(Option:Some(_)
) and Result:Err(_
) will be mapped to
Option:Some(Result:Ok(_)
) and Option:Some(Option:Err(_)
).
type SomeErr = {}
local x: Result<Option<number>, SomeErr> = Ok(Some(2))
local y: Option<Result<number, SomeErr>> = Some(Ok(2))
assert(x:transpose() == y)
Ok
Contains the success value.
Err
Contains the error value.
isOk
Returns true
if the result is Result:Ok.
local x: Result<number, string> = Ok(3)
assert(x:isOk())
x = Err("Some error message")
assert(not x:isOk())
isOkAnd
Returns true
if the result is Result:Ok and the value inside it matches a
predicate.
local x: Result<number, string> = Ok(2)
assert(x:isOkAnd(function(x) return x > 1 end))
x = Ok(0)
assert(not x:isOkAnd(function(x) return x > 1 end))
x = Err("hey")
assert(not x:isOkAnd(function(x) return x > 1 end))
isErr
Returns true
if the result is Result:Err.
local x: Result<number, string> = Ok(3)
assert(not x:isErr())
x = Err("Some error message")
assert(x:isErr())
isErrAnd
Returns true
if the result is Result:Err and the value inside it matches a
predicate.
local x: Result<number, string> = Ok(2)
assert(not x:isErrAnd(function(x) return x > 1 end))
x = Err(3)
assert(x:isErrAnd(function(x) return x > 1 end))
x = Err("hey")
assert(not x:isErrAnd(function(x) return x > 1 end))
map
Maps a Result<T, E> to Result<U, E> by applying a function to a contained Result:Ok value, leaving an Result:Err value untouched.
This function can be used to compose the results of two functions.
local lines = "1\n2\n3\n4\n"
function parseInt(x: string): Result<number, string>
local num = tonumber(x)
if num == nil then
return Err("not an integer")
end
return Ok(num)
end
for line in lines:split("\n") do
local number = parseInt(line)
if number:isOk() then
print(number:unwrap())
else
print("not a number!")
end
end
mapOr
Returns the provided default (if Result:Err), or applies a function to the contained value (if Result:Ok).
Arguments passed to Result:mapOr are eagerly evaluated; if you are passing the result of a function call, it is recommended to use Result:mapOrElse, which is lazily evaluated.
local x: Result<string, string> = Ok("foo")
assert(x:mapOr("bar", string.upper) == "FOO")
x = Err("foo")
assert(x:mapOr("bar", string.upper) == "bar")
mapOrElse
Maps a Result<T, E> to U by applying fallback function default to a contained Result:Err value, or function f to a contained Result:Ok value.
This function can be used to unpack a successful result while handling an error.
local x: Result<string, string> = Ok("foo")
assert(x:mapOrElse(function(x) return "error: ".. x end, string.upper) == "FOO")
x = Err("foo")
assert(x:mapOrElse(function(x) return "error: ".. x end, string.upper) == "error: foo")
mapErr
Maps a Result<T, E> to Result<T, F> by applying a function to a contained Result:Err value, leaving an Result:Ok value untouched.
This function can be used to pass through a successful result while handling an error.
local function stringify(x: number): string
return string.format("error code: %d", x)
end
local x: Result<number, number> = Ok(2)
assert(x:mapErr(stringify) == Ok(2))
x = Err(13)
assert(x:mapErr(stringify) == Err("error code: 13"))
inspect
Calls the provided closure with a reference to the contained value (if Result:Ok).
function parseInt(x: string): Result<number, string>
local num = tonumber(x)
if num == nil then
return Err("not an integer")
end
return Ok(num)
end
local x = parseInt("4")
:inspect(function(x) print("original: " .. x) end)
:map(function(x) return x ^ 3 end)
:expect("not an integer")
inspectErr
Calls the provided closure with a reference to the contained value (if Result:Err).
function parseInt(x: string): Result<number, string>
local num = tonumber(x)
if num == nil then
return Err("not an integer")
end
return Ok(num)
end
local x = parseInt("string")
:inspectErr(function(x) print("error: " .. x) end)
:map(function(x) return x ^ 3 end)
:unwrap()
expect
Returns the contained Result:Ok value, consuming the self value.
Because this function may panic, its use is generally discouraged. Instead, prefer to use Result:isErr and handle the Err case explicitly, or call Result:unwrapOr, Result:unwrapOrElse, or Result:unwrapOrDefault. Panics
local x: Result<number, string> = Err("emergency failure")
x:expect("Testing expect") -- panics with message `Testing expect: emergency failure`
Recommended Message Style
It is recommended that expect messages are used to describe the reason you expect the Result should be Result:Ok.
local process = require("@lune/process")
local function envVar<T>(var: string): Result<T, string>
local val = process.env[var]
if val == nil then
return Err("environment variable not found")
end
Ok(val)
end
local path = envVar("IMPORTANT_PATH")
:expect("env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`")
Hint: If you’re having trouble remembering how to phrase expect error messages remember to focus on the word “should” as in “env variable should be set by blah” or “the given binary should be available and executable by the current user”.
Errors
Type | Description |
---|---|
panic | If the value is a [Result:Err], with a panic message including the passed message, and the content of the `Err`. |
unwrap
Returns the Result:Ok.
Because this function may panic, its use is generally discouraged. Instead, prefer to use Result:unwrapOr, Result:unwrapOrElse, or Result:unwrapOrDefault.
local x: Result<number, string> = Ok(2)
assert(x:unwrap() == 2)
x = Err("oh no")
x:unwrap() -- panics with `oh no`
Errors
Type | Description |
---|---|
panic | Panics if the value is an [Result:Err], with a panic message provided by the [Result:Err]’s value. |
expectErr
Returns the contained Result:Err.
local x: Result<number, string> = Ok(10)
x:expectErr("Testing expect") -- panics with `Testing expect: 10`
Errors
Type | Description |
---|---|
panic | Panics if the value is an [Result:Ok], with a panic message including the passed message, and the content of the [Resul:Ok]. |
unwrapErr
Returns the contained Result:Err.
local x: Result<number, string> = Ok(2)
x:unwrapErr() -- panics with `2`
x = Err("oh no")
assert(x:unwrapErr() == "oh no")
Errors
Type | Description |
---|---|
panic | Panics if the value is an [Result:Ok], with a panic message provided by the [Result:Ok]’s value. |
and_
Returns res if the result is Result:Ok, otherwise returns the Result:Err value of self.
local x: Result<number, string> = Ok(2)
local y: Result<string, string> = Err("late error")
assert(x:and_(y) == Err("late error"))
local x: Result<number, string> = Err("early error")
local y: Result<string, string> = Ok("foo")
assert(x:and_(y) == Err("early error"))
local x: Result<number, string> = Err("not a 2")
local y: Result<string, string> = Err("late error")
assert(x:and_(y) == Err("not a 2"))
local x: Result<number, string> = Ok(2)
local y: Result<string, string> = Ok("different result type")
assert(x:and_(y) == Ok("different result type"))
andThen
Calls op
if the result is Result:Ok, otherwise returns the
Result:Err value of self
.
This function can be used for control flow based on Result values.
function sqThenToString(x): Result<string, string>
if typeof(sq) ~= "number" then
return Err("not a number: '" .. x .. "'")
end
return Ok(x ^ 2):map(function(sq)
return tostring(sq)
end)
end
assert(Ok(2):andThen(sqThenToString) == Ok("4"))
assert(Err("string"):andThen(sqThenToString) == Err("not a number: 'string'"))
Often used to chain fallible operations that may return Result:Err.
or_
Calls op
if the result is Result:Ok, otherwise returns the
Result:Err value of self
.
This function can be used for control flow based on Result values.
local x: Result<number, string> = Ok(2)
local y: Result<number, string> = Err("late error")
assert(x:or_(y) == Ok(2))
local x: Result<number, string> = Err("early error")
local y: Result<number, string> = Ok(2)
assert(x:or_(y) == Ok(2))
local x: Result<number, string> = Err("not a 2")
local y: Result<number, string> = Err("late error")
assert(x:or_(y) == Err("late error"))
local x: Result<number, string> = Ok(2)
local y: Result<number, string> = Ok(100)
assert(x:or_(y) == Ok(2))
orElse
Calls op
if the result is Result:Ok, otherwise returns the
Result:Err value of self
.
This function can be used for control flow based on Result values.
function sq(x: number): Result<number, number>
return Ok(x * x)
end
function err(x: number): Result<number, number>
return Err(x)
end
assert(Ok(2):orElse(sq):orElse(sq) == Ok(2))
assert(Ok(2):orElse(err):orElse(sq) == Ok(2))
assert(Err(3):orElse(sq):orElse(err) == Ok(9))
assert(Err(3):orElse(err):orElse(err) == Err(3))
unwrapOr
Returns the contained Result:Ok value or a provided default.
Arguments passed to Result:unwrapOr are eagerly evaluated; if you are passing the result of a function call, it is recommended to use Result:unwrapOrElse, which is lazily evaluated.
local default = 2
local x: Result<number, string> = Ok(9)
assert(x:unwrapOr(default), 9)
x = Err("error")
assert(x:unwrapOr(default), default)
unwrapOrElse
Returns the contained Result:Ok value or computes it from a closure.
function count(x: string): number
return #x
end
assert(Ok(2):unwrapOrElse(count) == 2))
assert(Err("foo"):unwrapOrElse(count) == 3))
contains
Returns true if the Result:Ok value is val
.
local x: Result<number, string> = Ok(2)
assert(x:contains(2))
x = Ok(3)
assert(not x:contains(2))
x = Err(2)
assert(not x:contains(2))
containsErr
Returns true if the Result:Err value is err
.
local x: Result<number, string> = Err(2)
assert(x:containsErr(2))
x = Err(3)
assert(not x:containsErr(2))
x = Ok(2)
assert(not x:containsErr(2))
display
Returns a formatted representation of the result, often used for printing to stdout.
local x: Result<number, string> = Ok(123)
local y: Result<number, string> = Err("error")
print(x:display()) -- prints `Result::Ok(123)`
print(y:display()) -- prints `Result::Err("error")`