lloyAlloy on GitHub
Result
Docs

Result

Updated
Result<T, E>
local r: Result<number, string> = Ok(1)
local v = r:unwrap_or(0)
match r with
    case Ok(n) then print(n)
    case Err(e) then warn(e)
end

Ok(value) or Err(error), from the std. A function that can fail returns one, and the caller reads it with a match, an if local Ok(v) = r, or the methods. try expr inside a function that returns a Result returns the Err early, and at the module top level it returns the Err from the chunk; try do ... end turns a throw into an Err; try await does the same for a Future.

An Err carries trace, a traceback Result.pcall and try fill in and Err(e, trace) sets by hand. map and map_err yield a Result of the same surface whose own map is any: Luau rejects an alias that names itself with other arguments. and_then(f) runs f on an Ok value and returns the Result f builds, or_else(f) does the same for an Err, and unwrap_or_else(f) calls f with the error only when there is one, so a costly fallback runs only when it is needed. Result<T, E> is covariant, so a Result<Boost, E> passes where Result<any, E> is asked.

Statics

Result.pcall

Result.pcall<T>(f: (...any) -> T, ...: any): Result<T, string>

Calls f with the arguments after it: Ok of what it returns, or Err of what it threw. The value type follows f, and the Err carries the traceback in trace. A function that returns nothing gives Result<nil, string>.

local r = Result.pcall(string.rep, "a", 3)
print(r:unwrap_or(""))

Methods

Result:unwrap

Result:unwrap(): T

The value of an Ok. An Err raises, with the error and its trace in the message. Reach for it when an Err is a bug, not a case.

local r: Result<number, string> = Ok(1)
print(r:unwrap())

Result:expect

Result:expect(message: string): T

The value of an Ok. An Err raises with message, which says what the caller expected.

local r: Result<number, string> = Ok(1)
print(r:expect("the config sets a width"))

Result:unwrap_or

Result:unwrap_or<D>(default: D): T | D

The value of an Ok, or default. The result type is T | D, so r:unwrap_or(nil) is T?.

local r: Result<number, string> = Err("no")
print(r:unwrap_or(0))

Result:map

Result:map<U>(f: (T) -> U): Result<U, E>

Ok(f(value)), or the same Err untouched. The mapped Result names a second alias, so a third map in one chain types as any.

local r: Result<number, string> = Ok(2)
print(r:map(function(n) return n * 2 end):unwrap())

Result:map_err

Result:map_err<F>(f: (E) -> F): Result<T, F>

Err(f(error)), or the same Ok untouched. Use it to turn a low level error into one the caller names.

local r: Result<number, string> = Err("io")
print(r:map_err(function(e) return `read: {e}` end):is_err())

Result:is_ok

Result:is_ok(): boolean

Whether the Result is an Ok. It narrows nothing; a match or if local Ok(v) = r binds the value.

local r: Result<number, string> = Ok(1)
print(r:is_ok())

Result:is_err

Result:is_err(): boolean

Whether the Result is an Err.

local r: Result<number, string> = Err("no")
print(r:is_err())

Result:ok

Result:ok(): T?

The value of an Ok, or nil for an Err. It drops the error, which is what ?? wants on the other side.

local r: Result<number, string> = Ok(1)
print(r:ok() ?? 0)

Fields

Result.tag

Result.tag: "Ok" | "Err"

Which case the Result is, as a string. A match reads it for you; the field is there for code that stores or sends the tag.

local r: Result<number, string> = Ok(1)
print(r.tag)

Result.trace

Result.trace: string?

The traceback of an Err. try and Result.pcall fill it in, Err(e, trace) sets it by hand, and unwrap prints it under the error.

local r = Result.pcall(error, "boom")
print(r.trace ?? "no trace")