Description
Current Situation
The C++ binding defines a custom tidesdb::Exception (derived from std::runtime_error) and throws it on most error paths (failed opens, I/O errors, conflicts, invalid arguments, etc.). While this is convenient and idiomatic for many C++ applications, it introduces noticeable overhead in performance-critical code:
- Stack unwinding cost (even with
-fno-exceptions disabled in hot paths)
- Difficulty in writing zero-overhead, no-throw hot loops (common in trading systems, time-series ingestion, ML feature stores, etc.)
- Less explicit control flow — callers must either use try/catch everywhere or let exceptions propagate
The library already maintains a rich ErrorCode enum, which makes it a perfect candidate for modern value-based error handling.
Proposed Improvement
Introduce std::expected<T, ErrorCode> (or a small wrapper Expected<T>) as the primary return type for operations that can fail, while optionally keeping the exception-based API for backward compatibility.
Benefits:
- Zero runtime cost on the success path (no exceptions, no unwinding)
- Explicit, forced error handling at the call site → fewer swallowed errors
- Better performance in tight loops and hot paths (critical for your use case and many other high-throughput workloads)
- Composable with modern C++ (monadic operations like
.and_then(), .or_else(), | in C++23)
- Cleaner integration with
std::span changes
- Aligns with trends in high-performance libraries
Backward Compatibility
Provide both APIs:
- Keep existing exception-throwing methods (perhaps under a different name or via a template policy)
- Or add
_expected / _noexcept suffixed versions that return std::expected
Concrete Suggestions
-
Helper type (at the top of the header):
template<typename T = void>
using Expected = std::expected<T, ErrorCode>;
-
Example API changes (add alongside existing methods):
// TidesDB
[[nodiscard]] Expected<void> createColumnFamily(const std::string& name,
const ColumnFamilyConfig& config);
[[nodiscard]] Expected<ColumnFamily> getColumnFamily(const std::string& name);
void commit(); // existing (throws)
[[nodiscard]] Expected<void> commit_expected(); // new
// Iterator, ColumnFamily methods, etc. similarly
-
Error conversion utilities:
[[nodiscard]] static Expected<void> fromCError(int c_err); // maps tidesdb error codes
-
C++ Standard
Lifetime / Design Notes
- Success path returns the value directly.
- Failure path carries the precise
ErrorCode (already beautifully defined).
- For operations that currently return
std::vector<std::uint8_t> on "not found", we can decide between Expected<std::vector<...>> (empty on not-found) or using ErrorCode::NotFound explicitly.
Motivation / Use Case
In low-latency / high-frequency applications, exception-based error handling is often deliberately avoided. Value-based errors with std::expected provide the best of both worlds: safety and performance.
Description
Current Situation
The C++ binding defines a custom
tidesdb::Exception(derived fromstd::runtime_error) and throws it on most error paths (failed opens, I/O errors, conflicts, invalid arguments, etc.). While this is convenient and idiomatic for many C++ applications, it introduces noticeable overhead in performance-critical code:-fno-exceptionsdisabled in hot paths)The library already maintains a rich
ErrorCodeenum, which makes it a perfect candidate for modern value-based error handling.Proposed Improvement
Introduce
std::expected<T, ErrorCode>(or a small wrapperExpected<T>) as the primary return type for operations that can fail, while optionally keeping the exception-based API for backward compatibility.Benefits:
.and_then(),.or_else(),|in C++23)std::spanchangesBackward Compatibility
Provide both APIs:
_expected/_noexceptsuffixed versions that returnstd::expectedConcrete Suggestions
Helper type (at the top of the header):
Example API changes (add alongside existing methods):
Error conversion utilities:
C++ Standard
std::expected.tl::expectedfrom https://github.com/TartanLlama/expected) or keep exceptions as the default.Lifetime / Design Notes
ErrorCode(already beautifully defined).std::vector<std::uint8_t>on "not found", we can decide betweenExpected<std::vector<...>>(empty on not-found) or usingErrorCode::NotFoundexplicitly.Motivation / Use Case
In low-latency / high-frequency applications, exception-based error handling is often deliberately avoided. Value-based errors with
std::expectedprovide the best of both worlds: safety and performance.