Skip to content

KodeXMachina/Mustu

Repository files navigation

mustu

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 chainingAndThen, 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.


Why another expected-like type?

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).


Quick start

#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;
}

Building

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-failure

If EVersion is installed under a custom prefix, pass it through CMake:

cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/eversion

CMake 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.

Local quality

Use the local quality gate before publishing changes:

python3 scripts/quality.py check

The 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.

Use it from another project

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)

Examples

The examples/ directory has five runnable programs that double as tutorials:

After building, run e.g. ./build/examples/monadic_chain.


Public API surface

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.

Version metadata

<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";

Custom error types

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);

-fno-exceptions

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.

Replaceable fatal handler

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.


Design notes

  • 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.

License

This repository currently carries the GNU GPL v3 license. See LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Generated from winterYANGWT/Cpp-Dev