-
Notifications
You must be signed in to change notification settings - Fork 7
feat(error_handling): Add Result<T, E>
alias and macros to enable ergonomic Rust-style error handling.
#47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
5080997
Add Result<ReturnType> and unit tests
Bill-hbrhbr c25c310
Merge branch 'main' into add-result-utilities
Bill-hbrhbr db9fe23
Rename macros and change underlying implementation to use OUTCOME_TRY…
Bill-hbrhbr 8f07152
Improve comments for result macros
Bill-hbrhbr 2ea95f2
Add ERROR_HANDLING to make macros more explicit
Bill-hbrhbr 6361e25
Fix NOLINT comments
Bill-hbrhbr 8c210ca
Apply suggestions from code review
Bill-hbrhbr cd779a9
Simply macros
Bill-hbrhbr 943154e
lint fix
Bill-hbrhbr c1e11dd
Add unit test cases for unique ptr
Bill-hbrhbr b4aa46b
Remove unnecessary parenthesis
Bill-hbrhbr a5707a9
Merge branch 'main' into add-result-utilities
Bill-hbrhbr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#ifndef YSTDLIB_ERROR_HANDLING_RESULT_HPP | ||
#define YSTDLIB_ERROR_HANDLING_RESULT_HPP | ||
|
||
#include <system_error> | ||
|
||
#include <outcome/config.hpp> | ||
#include <outcome/std_result.hpp> | ||
#include <outcome/success_failure.hpp> | ||
#include <outcome/try.hpp> | ||
|
||
namespace ystdlib::error_handling { | ||
/** | ||
* A Rust-style `Result<T, E>` type for standardized, exception-free error handling. | ||
* | ||
* This alias standardizes error handling across the codebase by defaulting the error type to | ||
* `std::error_code`, which interoperates with the `ystdlib::error_handling::ErrorCode`, making it | ||
* easier to compose errors and propagate them across different modules and libraries. | ||
* | ||
* @tparam ReturnType The type returned on success. | ||
* @tparam ErrorType The type used to represent errors. | ||
*/ | ||
template <typename ReturnType, typename ErrorType = std::error_code> | ||
using Result = OUTCOME_V2_NAMESPACE::std_result<ReturnType, ErrorType>; | ||
|
||
/** | ||
* @return A value indicating successful completion of a function that returns a void result (i.e., | ||
* `Result<void, E>`). | ||
*/ | ||
[[nodiscard]] inline auto success() -> OUTCOME_V2_NAMESPACE::success_type<void> { | ||
return OUTCOME_V2_NAMESPACE::success(); | ||
} | ||
|
||
/** | ||
* A function-style macro that emulates Rust’s try (`?`) operator for error propagation. | ||
* | ||
* @param expr An expression that evaluates to a `Result` object. | ||
* | ||
* Behavior: | ||
* - If `expr` represents an error (i.e., `expr.has_error()` returns true), the macro performs an | ||
* early return from the enclosing function with the contained error. | ||
* - Otherwise, it unwraps and yields the successful value as an rvalue reference (`expr.value()`). | ||
* | ||
* NOTE: This macro is only supported on GCC and Clang due to reliance on compiler-specific | ||
* extensions. | ||
*/ | ||
#ifdef OUTCOME_TRYX | ||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) | ||
#define YSTDLIB_ERROR_HANDLING_TRYX(expr) OUTCOME_TRYX(expr) | ||
#endif | ||
|
||
/** | ||
* A function-style macro for propagating errors from expressions that evaluate to a void result | ||
* (`Result<void, E>`). | ||
* | ||
* @param expr An expression that evaluates to a `Result<void, E>` object. | ||
* | ||
* Behavior: | ||
* - If `expr` represents an error (i.e., `expr.has_error()` returns true), the macro performs an | ||
* early return from the enclosing function with the contained error. | ||
* - Otherwise, execution continues normally. | ||
*/ | ||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) | ||
#define YSTDLIB_ERROR_HANDLING_TRYV(expr) OUTCOME_TRYV(expr) | ||
} // namespace ystdlib::error_handling | ||
|
||
#endif // YSTDLIB_ERROR_HANDLING_RESULT_HPP |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#include <memory> | ||
#include <system_error> | ||
#include <type_traits> | ||
|
||
#include <ystdlib/error_handling/Result.hpp> | ||
|
||
#include <catch2/catch_test_macros.hpp> | ||
|
||
#include "types.hpp" | ||
|
||
using ystdlib::error_handling::Result; | ||
using ystdlib::error_handling::success; | ||
using ystdlib::error_handling::test::AlwaysSuccessErrorCode; | ||
using ystdlib::error_handling::test::AlwaysSuccessErrorCodeEnum; | ||
using ystdlib::error_handling::test::BinaryErrorCode; | ||
using ystdlib::error_handling::test::BinaryErrorCodeEnum; | ||
|
||
namespace { | ||
constexpr int cTestInt{123}; | ||
constexpr auto cVoidFunc = [](bool is_error) -> Result<void> { | ||
if (is_error) { | ||
return BinaryErrorCode{BinaryErrorCodeEnum::Failure}; | ||
} | ||
return success(); | ||
}; | ||
constexpr auto cIntFunc = [](bool is_error) -> Result<int> { | ||
if (is_error) { | ||
return std::errc::bad_message; | ||
} | ||
return cTestInt; | ||
Bill-hbrhbr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
constexpr auto cUniquePtrFunc = [](bool is_error) -> Result<std::unique_ptr<int>> { | ||
if (is_error) { | ||
return AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success}; | ||
} | ||
return std::make_unique<int>(cTestInt); | ||
}; | ||
} // namespace | ||
|
||
namespace ystdlib::error_handling::test { | ||
TEST_CASE("test_result_void", "[error_handling][Result]") { | ||
auto const result_no_error{cVoidFunc(false)}; | ||
REQUIRE_FALSE(result_no_error.has_error()); | ||
REQUIRE(std::is_void_v<decltype(result_no_error.value())>); | ||
|
||
auto const result_has_error{cVoidFunc(true)}; | ||
REQUIRE(result_has_error.has_error()); | ||
REQUIRE(BinaryErrorCode{BinaryErrorCodeEnum::Failure} == result_has_error.error()); | ||
} | ||
|
||
TEST_CASE("test_result_void_in_main", "[error_handling][Result]") { | ||
auto main_func = [&](bool is_error) -> Result<void> { | ||
YSTDLIB_ERROR_HANDLING_TRYV(cVoidFunc(is_error)); | ||
return success(); | ||
}; | ||
auto const main_no_error{main_func(false)}; | ||
REQUIRE_FALSE(main_no_error.has_error()); | ||
REQUIRE(std::is_void_v<decltype(main_no_error.value())>); | ||
|
||
auto const main_has_error{main_func(true)}; | ||
REQUIRE(main_has_error.has_error()); | ||
REQUIRE(BinaryErrorCode{BinaryErrorCodeEnum::Failure} == main_has_error.error()); | ||
} | ||
|
||
TEST_CASE("test_result_int", "[error_handling][Result]") { | ||
auto const result_no_error{cIntFunc(false)}; | ||
REQUIRE_FALSE(result_no_error.has_error()); | ||
REQUIRE(cTestInt == result_no_error.value()); | ||
|
||
auto const result_has_error{cIntFunc(true)}; | ||
REQUIRE(result_has_error.has_error()); | ||
REQUIRE(std::errc::bad_message == result_has_error.error()); | ||
} | ||
|
||
TEST_CASE("test_result_int_in_main", "[error_handling][Result]") { | ||
auto main_func = [&](bool is_error) -> Result<void> { | ||
YSTDLIB_ERROR_HANDLING_TRYV(cIntFunc(is_error)); | ||
return success(); | ||
}; | ||
auto const main_no_error{main_func(false)}; | ||
REQUIRE_FALSE(main_no_error.has_error()); | ||
REQUIRE(std::is_void_v<decltype(main_no_error.value())>); | ||
|
||
auto const main_has_error{main_func(true)}; | ||
REQUIRE(main_has_error.has_error()); | ||
REQUIRE(std::errc::bad_message == main_has_error.error()); | ||
} | ||
|
||
TEST_CASE("test_result_int_propagate", "[error_handling][Result]") { | ||
auto main_func = [&](bool is_error) -> Result<int> { | ||
return YSTDLIB_ERROR_HANDLING_TRYX(cIntFunc(is_error)); | ||
}; | ||
auto const main_no_error{main_func(false)}; | ||
REQUIRE_FALSE(main_no_error.has_error()); | ||
REQUIRE(cTestInt == main_no_error.value()); | ||
|
||
auto const main_has_error{main_func(true)}; | ||
REQUIRE(main_has_error.has_error()); | ||
REQUIRE(std::errc::bad_message == main_has_error.error()); | ||
} | ||
|
||
TEST_CASE("test_result_unique_ptr", "[error_handling][Result]") { | ||
auto const result_no_error{cUniquePtrFunc(false)}; | ||
REQUIRE_FALSE(result_no_error.has_error()); | ||
REQUIRE(cTestInt == *(result_no_error.value())); | ||
|
||
auto const result_has_error{cUniquePtrFunc(true)}; | ||
REQUIRE(result_has_error.has_error()); | ||
REQUIRE(AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} == result_has_error.error() | ||
); | ||
} | ||
|
||
TEST_CASE("test_result_unique_ptr_in_main", "[error_handling][Result]") { | ||
auto main_func = [&](bool is_error) -> Result<void> { | ||
YSTDLIB_ERROR_HANDLING_TRYV(cUniquePtrFunc(is_error)); | ||
return success(); | ||
}; | ||
auto const main_no_error{main_func(false)}; | ||
REQUIRE_FALSE(main_no_error.has_error()); | ||
REQUIRE(std::is_void_v<decltype(main_no_error.value())>); | ||
|
||
auto const main_has_error{main_func(true)}; | ||
REQUIRE(main_has_error.has_error()); | ||
REQUIRE(AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} == main_has_error.error()); | ||
} | ||
|
||
TEST_CASE("test_result_unique_ptr_propagate", "[error_handling][Result]") { | ||
auto main_func = [&](bool is_error) -> Result<std::unique_ptr<int>> { | ||
return YSTDLIB_ERROR_HANDLING_TRYX(cUniquePtrFunc(is_error)); | ||
}; | ||
auto const main_no_error{main_func(false)}; | ||
REQUIRE_FALSE(main_no_error.has_error()); | ||
REQUIRE(cTestInt == *(main_no_error.value())); | ||
|
||
auto const main_has_error{main_func(true)}; | ||
REQUIRE(main_has_error.has_error()); | ||
REQUIRE(AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} == main_has_error.error()); | ||
} | ||
} // namespace ystdlib::error_handling::test |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.