-- Lua-implemented part of the utils module, extending the global utils table. -- The native parts of the module (dump, toJSON, fromJSON, NULL, isNull) -- are declared in Rust before this file is loaded. -- Substituted when a callback throws a nil error value (e.g. plain `error()`), -- so that the returned err is always non-nil on failure. local NIL_ERROR_PLACEHOLDER = "unspecified error" --- @brief Call a function, capturing any error - like pcall, but with Go-style returns. --- --- local value, err = utils.try(callback) --- local value, err = utils.try(callback, arg1, arg2) -- pcall-style shorthand --- if err then --- print(err) --- else --- -- handle value --- end --- --- - err is nil if the callback succeeded, otherwise the error value --- - value is the callback's first return value (nil on error) --- - extra arguments are passed to the callback, like with pcall --- --- This is less confusing than pcall when the return value is actually needed. --- The thrown error value is passed through as-is, so a table thrown with --- error({code = 42}) stays a table. The downside is that the result can't be --- used directly as a condition in "if" or "while" - simply use pcall there. --- --- @param callback function: Function to call --- @param ... any: Arguments passed to the callback --- @return any, any: The callback's first return value and nil, or nil and the error function utils.try(callback, ...) if type(callback) ~= "function" then error("utils.try: callback is not a function (got " .. type(callback) .. ")") end local ok, result = pcall(callback, ...) if ok then return result, nil end if result == nil then result = NIL_ERROR_PLACEHOLDER end return nil, result end local TRYN_MAX_COUNT = 64 --- @brief Call a function, capturing any error - like utils.try, but for N return values. --- --- local value1, value2, err = utils.tryn(2, callback) --- local value1, value2, err = utils.tryn(2, callback, arg1, arg2) -- pcall-style shorthand --- if err then --- print(err) --- else --- -- handle (value1, value2) --- end --- --- - err is nil if the callback succeeded, otherwise the error value --- - values are the callback's return values, padded with nils or truncated to exactly N --- - extra arguments are passed to the callback, like with pcall --- --- This is less confusing than pcall for multiple return values, where the error --- and the first value share a slot: --- --- local status, errorOrValue1, value2 = pcall(callback) --- --- @param expectedCount number: How many return values to pass through (integer, 0 to 64) --- @param callback function: Function to call --- @param ... any: Arguments passed to the callback --- @return any ...: expectedCount values followed by the error or nil function utils.tryn(expectedCount, callback, ...) local count = math.tointeger(expectedCount) if count == nil then error("utils.tryn: expectedCount must be an integer number") end expectedCount = count if expectedCount < 0 then error("utils.tryn: expectedCount must not be negative") end if expectedCount > TRYN_MAX_COUNT then error("utils.tryn: at most " .. TRYN_MAX_COUNT .. " return values are supported") end if type(callback) ~= "function" then error("utils.tryn: callback is not a function (got " .. type(callback) .. ")") end local results = table.pack(pcall(callback, ...)) if results[1] then -- Success: pass through values 2..expectedCount+1 (truncating or nil-padding), -- with nil in the error slot. The explicit assignment overwrites a possible -- extra return value so it cannot leak into the error slot. results[expectedCount + 2] = nil return table.unpack(results, 2, expectedCount + 2) end local err = results[2] if err == nil then err = NIL_ERROR_PLACEHOLDER end local out = {} out[expectedCount + 1] = err return table.unpack(out, 1, expectedCount + 1) end