Forest Logo
search
package_2

resulty

By @yoheiyayoi

Roblox

Mirrored

Resulty

Rust-style Result type for Luau. No more pcall spaghetti.

Why?

Ever get tired of writing this?

local ok, result = pcall(function()
    return something:ThatMightFail()
end)

if ok then
    -- do stuff
else
    -- handle error
end

Yeah, me too. This gives you proper error handling that you can actually chain together.

Install

Wally:

[dependencies]
Resulty = "yohei_yayoi/resulty@1.0.0"

Or just grab src/init.luau and drop it in your project.

Quick Example

local Resulty = require(path.to.Resulty)

local result = Resulty.try(function()
    return somethingRisky()
end)

result:match({
    Ok = function(value) print("got:", value) end,
    Err = function(err) warn("oops:", err) end,
})

API

Making Results

Resulty.Ok(value) - wrap a success value

local good = Resulty.Ok("nice")

Resulty.Err(error) - wrap an error

local bad = Resulty.Err("something broke")

Resulty.try(fn, ...) - pcall but better

local result = Resulty.try(function()
    return workspace:FindFirstChild("Part").Name
end)

Resulty.tryWith(context, fn, ...) - same as try but adds context to errors

local result = Resulty.tryWith("loading data", function()
    return DataStore:GetAsync(key)
end)
-- if it fails: "loading data: [actual error]"

Resulty.validate(value, check, errMsg?) - validate something

local result = Resulty.validate(age, function(v) return v >= 18 end, "too young")

Combining Results

Resulty.all(results) - all must succeed, returns array of values

local all = Resulty.all({
    Resulty.Ok(1),
    Resulty.Ok(2),
})
-- Ok({1, 2})

Resulty.any(results) - returns first success

local first = Resulty.any({
    Resulty.Err("nope"),
    Resulty.Ok("this one"),
})

Checking

:isOk() / :isErr() - what it sounds like

Getting Values Out

:unwrap() - get the value or explode

local val = result:unwrap() -- errors if result is Err!

:unwrapOr(default) - get the value or use fallback

local name = result:unwrapOr("Unknown")

:unwrapOrElse(fn) - get the value or compute fallback

local val = result:unwrapOrElse(function(err)
    warn(err)
    return getFallback()
end)

:ok() / :err() - returns value/error or nil

Pattern Matching

:match(handlers) - handle both cases

result:match({
    Ok = function(v) return "got " .. v end,
    Err = function(e) return "failed: " .. e end,
})

Transforming

:map(fn) - transform success value

Resulty.Ok(5):map(function(n) return n * 2 end) -- Ok(10)

:mapErr(fn) - transform error value

:filter(predicate, errMsg?) - keep value if predicate passes

Resulty.Ok(5):filter(function(n) return n % 2 == 0 end, "not even")
-- Err("not even")

Chaining

:andThen(fn) - chain operations (fn must return a Result)

Resulty.Ok(userId)
    :andThen(function(id)
        return Resulty.try(function()
            return DataStore:GetAsync(id)
        end)
    end)

:orElse(fn) - try recovery on error

fetchPrimary()
    :orElse(function(err)
        warn("primary failed:", err)
        return fetchBackup()
    end)

Debugging

:inspect(fn) - peek at success value (for logging)

result:inspect(function(v) print("got:", v) end)

:inspectErr(fn) - peek at error value

Logic

:and_(other) - returns other if self is Ok :or_(other) - returns other if self is Err

Promise-ish

:asPromise() - if you want that interface

result:asPromise()
    :andThen(function(v) return v * 2 end)
    :catch(function(e) return 0 end)

Real Example

local function loadPlayerData(player)
    return Resulty.tryWith("loading " .. player.Name, function()
        return DataStore:GetAsync(player.UserId)
    end)
    :andThen(function(data)
        return Resulty.validate(data, function(d)
            return d ~= nil and d.version ~= nil
        end, "bad data format")
    end)
    :map(migrateData)
end

loadPlayerData(player):match({
    Ok = function(data) applyData(player, data) end,
    Err = function(err)
        warn(err)
        applyDefaults(player)
    end,
})

License

MIT

Author

yooo_ / YoheiKung (Roblox) / yohei_yayoi (Discord, GitHub)

Package Details

Install command (Click to copy)


Version

1.0.1

License

MIT

check_circle

Safe for commercial use

Automated license review — not legal advice.