Skip to content
Jack Gerrits edited this page Apr 23, 2023 · 22 revisions

⚠️ This page is not final and not decided or agreed upon yet. It is in draft form for if we decide to move forward with this strategy.

Error Handling in VW

Starting in VW 10, there is a new error handling system. The system is very similar to how Rust handles error.

The most important type is VW::result<T> which is a type alias for tl::expected<T, VW::error>. This type is used to return either a value or an error. For functions with no return value, VW::result<void> is used.

The VW::error type contains an error code enum and an error message. The error code enum is used to determine the type of error that occurred. The error message is a string that contains more information about the error.

Therefore, for all functions which can fail VW::result<T> should be used. All functions which return VW::result<T> should be marked with [[nodiscard]] to ensure that the error is checked.

However, for situations which a failure indicates a programming bug and not an error that should be recoverable. VW_THROW should be used, which will by default throw an exception. There are a few different defines that can be used to control the behavior of VW_THROW (only 1 should be defined at a time):

  • VW_NOEXCEPT - If this is defined, then VW_THROW will print a diagnostic and call std::abort instead of throwing an exception.
  • VW_NOEXCEPT_IGNORE - If this is defined, then VW_THROW will just print a diagnostic.
  • VW_NOEXCEPT_IGNORE_SILENT - If this is defined, then VW_THROW will do nothing at all.

NOTE: if using VW_NOEXCEPT_IGNORE or VW_NOEXCEPT_IGNORE_SILENT, program behavior is undefined behavior after encountering the first unrecoverable error due to the way it is handled.

Failure to allocate memory is handled with the above VW_THROW semantics and std::bad_alloc.

Therefore, a consumer of VowpalWabbit could decide to catch these very few exceptions and handle them in a way that is appropriate for their application. But in general, these represent programming bugs and should be fixed or unrecoverable errors.

Using VW::result<T>

To return the expected value, simply return the value:

VW::result<int> foo() { return 5; }

To return success in a void function:

VW::result<void> foo() { return {}; }

To return a generic error, use VW::make_error:

VW::result<int> foo() { return VW::make_error("Function failed."); }

To return a specific error code:

VW::result<int> foo() { return VW::make_error(VW::error_code::IO, "File operation failed."); }

To check if the result is an error or value, use VW::result<T>::has_value(). It is also convertible to a bool. Because VW::result<T> follows value semantics there are many ways to properly handle and use the type.

auto result = foo();
if (result) { std::cout << "Success! Value is: " << *result; }
else { return result; }

// or
ASSIGN_OR_RETURN(result, foo());
std::cout << "Success! Value is: " << *result;

// or
auto result = foo().map([](int value) { std::cout << "Success! Value is: " << value; });
if (!result) { return result; }

Light errors

There is a compile feature called LIGHT_ERRORS which, when enabled, will make the error type only contain an error code enum. This is useful for when you don't need the error message. This is disabled by default. When enabled, rich and dynamic error messages are not available.

This turns VW::error into a trivially copyable type which may be useful for performance. However, it is unclear if this actually helps. Please benchmark and compare if you are interested in using this feature.

Clone this wiki locally