A C++20 header-only
Result<T, E>library that must be used.
mustu is a small, zero-overhead, zero-macro alternative to throwing
exceptions or returning bare std::expected. It pushes "did you handle the
error?" from a runtime concern (assertions, sanitizers, code review) to a
compile-time concern ([[nodiscard]] on the type itself), and it gives
you a fluent, macro-free way to compose fallible code:
- Monadic chaining —
AndThen,Transform,OrElse,TransformError,Inspect,AndThenIf,ValueOrElse, …
Version constants are generated by EVersion through CMake and exposed from
<mustu/version_info.h>. A thin set of portable statement-form macros
(MUSTU_ASSIGN_OR_RETURN and MUSTU_RETURN_IF_ERROR) is also shipped for
people who want them, but they are strictly opt-in
(<mustu/try_macros.h>) and the rest of the library never depends on them.
| Concern | throw |
std::expected |
mustu::Result |
|---|---|---|---|
| Visible in function signature | no | yes | yes |
| Forces caller to acknowledge error | n/a | runtime only | compile-time [[nodiscard]] |
Works with -fno-exceptions |
no | yes | yes |
| Monadic combinators (4 ref-quals) | n/a | yes | yes |
| Header-only package | n/a | std-lib only | yes, with EVersion metadata |
| Replaceable fatal handler | std::terminate | std::terminate | mustu::SetFatalHandler |
The driving idea: if a function returns Result<T, E>, it should be
impossible to compile code that silently throws the result away. mustu
relies on the type's own [[nodiscard]] plus the ref-qualified combinators
to make discarded results a warning (and an error under -Werror).
#include <mustu/mustu.h>
using mustu::Err;
using mustu::Result;
mustu::Result<int> Parse(std::string_view s); // returns Err(...) on failure
int main() {
// Monadic — no macros, no exceptions, no early return.
auto out = Parse("42")
.AndThen([](int v) -> Result<int> { return v * 2; })
.Transform([](int v) { return v + 1; })
.ValueOrElse([](const mustu::Error& e) {
std::cerr << e.CodeMessage() << "\n";
return -1;
});
}If you want statement-form early return:
#include <mustu/try_macros.h>
mustu::Result<int> Pipeline(std::string_view s) {
MUSTU_ASSIGN_OR_RETURN(int v, Parse(s));
return v * 2 + 1;
}Requirements: a C++20 compiler (Clang 14+, GCC 11+, MSVC 19.30+), CMake
3.21+, and an installed/discoverable EVersion package
(find_package(EVersion CONFIG REQUIRED)).
cmake -S . -B build
cmake --build build -j
ctest --test-dir build --output-on-failureIf EVersion is installed under a custom prefix, pass it through CMake:
cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/eversionCMake options:
| Option | Default | Meaning |
|---|---|---|
MUSTU_BUILD_EXAMPLES |
top project only | Build the programs under examples/. |
MUSTU_BUILD_TESTS |
top project only | Build and register CTest cases under tests/. |
MUSTU_INSTALL |
top project only | Generate install + find_package machinery. |
MUSTU_NO_EXCEPTIONS |
OFF |
Build the extra -fno-exceptions test variant. |
Use the local quality gate before publishing changes:
python3 scripts/quality.py checkThe script formats project-owned C++ files, verifies strict C++20 compile
flags, runs CTest through the quality CMake preset, and runs
clang-tidy on source translation units plus generated header smoke
translation units. For this header-only library, those smoke
translation units make every project header a direct include check. See
scripts/README.md for detailed usage and file scope rules.
After cmake --install build --prefix <somewhere>:
find_package(Mustu CONFIG REQUIRED) # also resolves EVersion
target_link_libraries(my_app PRIVATE Mustu::Mustu)Or vendor it as a subdirectory:
# EVersion must already be available through find_package(), or added by the
# parent project before Mustu is configured.
add_subdirectory(third_party/Mustu)
target_link_libraries(my_app PRIVATE Mustu::Mustu)The examples/ directory has five runnable programs that double as
tutorials:
- examples/umbrella_header.cc — the
zero-macro core through
<mustu/mustu.h>. - examples/basic_usage.cc — first taste:
construction,
HasValue(), monadic recovery. - examples/monadic_chain.cc — full tour of the
combinators (linear pipeline, conditional fallback, lazy fallback,
TransformErrorto remap error types,Inspecttaps). - examples/custom_error.cc — trait-based
downstream error categories with
mustu::Error. - examples/try_macros.cc — opt-in statement-form
early return with
MUSTU_ASSIGN_OR_RETURNandMUSTU_RETURN_IF_ERROR.
After building, run e.g. ./build/examples/monadic_chain.
Everything lives in namespace mustu. The umbrella header is
include/mustu/mustu.h which pulls in:
| Header | What it gives you |
|---|---|
| mustu/result.h | Result<T, E>, Result<void, E>, Ok(), monadic ops. |
| mustu/failure.h | Failure<E>, Err(e) factory, ErrorLike concept. |
| mustu/error.h | Default Error wrapping std::error_code + owned msg. |
| mustu/error_category.h | Trait-based registration for custom error enums. |
| mustu/fatal.h | SetFatalHandler for unrecoverable bugs. |
<mustu/version_info.h> |
EVersion-generated version constants. |
| mustu/try_macros.h | Opt-in portable statement-form early-return macros. |
<mustu/version_info.h> exposes EVersion's generated constants:
#include <mustu/version_info.h>
static_assert(mustu::version_info::kVersionMajor ==
mustu::version_info::kVersion.major);
std::cout << mustu::version_info::kVersionString << "\n";Any type that satisfies the ErrorLike concept (basically: not the same as
T, movable) can be the error half of a Result. Use a domain enum, a
std::string, a struct — whatever fits. The default mustu::Error is just
a convenience.
enum class AppErr { kBadInput, kInternal };
mustu::Result<int, AppErr> Open(std::string_view name);The whole library compiles and runs cleanly with exceptions disabled: it
never throws, and no part of the public API depends on __cpp_exceptions.
The CMake option MUSTU_NO_EXCEPTIONS=ON turns on a second test target that
rebuilds the suite with -fno-exceptions to keep this guarantee honest.
mustu does not throw, even on misuse. Contract violations that can't be
expressed through the type system — calling Value() / operator* on an
errored Result, or Error() on a value — are unrecoverable: they
funnel through a single replaceable hook and then terminate the process.
mustu::SetFatalHandler([](const mustu::FatalContext& ctx) noexcept {
// log ctx.reason, ctx.file, ctx.line, ctx.function, then terminate.
});The handler type is void(*)(const FatalContext&) noexcept, so a handler
cannot throw to turn misuse into a recoverable error; if it returns,
InvokeFatal falls back to std::abort(). The default handler prints the
context to stderr and calls std::abort(). The takeaway: handle errors
through HasValue() / monadic ops — reaching the fatal path means a bug.
- PLAN.md — the design log: why
[[nodiscard]], how the monadic core was chosen, the six "deepening" Q&A, and §13's zero-macro deep dive. - QA.md and QA_ANSWERS.md — review questions and answers captured during the design pass.
This repository currently carries the GNU GPL v3 license. See LICENSE.