# How to Migrate from std::optional to absl::StatusOr for Error Handling

> Learn how to migrate from std::optional to absl::StatusOr for robust C++ error handling. Safely manage success and failure scenarios in your code.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: migration-guide
- Published: 2026-07-11

---

**To migrate from `std::optional` to `absl::StatusOr`, replace your return type with `absl::StatusOr<T>`, return values directly on success, return `absl::Status` errors on failure, and update callers to check `ok()` before accessing the value via `operator*` or `value()`.**

When building robust C++ applications with the [Abseil](https://github.com/abseil/abseil-cpp) library, error handling requires more expressiveness than `std::optional` can provide. While `std::optional` only indicates whether a value exists, `absl::StatusOr<T>` carries a rich error payload that explains why an operation failed. This guide shows you how to migrate existing code using the actual implementation details found in [`absl/status/statusor.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/statusor.h).

## Why Replace std::optional with absl::StatusOr?

`absl::StatusOr<T>` is a **union** that holds either a value of type `T` **or** an `absl::Status` object. Unlike `std::optional<T>`, which only records whether a value is present, `StatusOr` also conveys detailed error information when a value is absent.

| Feature | `std::optional<T>` | `absl::StatusOr<T>` |
|---------|-------------------|---------------------|
| **Success indication** | `has_value()` | `ok()` |
| **Error information** | None (silent absence) | Full `absl::Status` with code and message |
| **Value access** | `operator*`, `operator->`, `value()` | `operator*`, `operator->`, `value()`, `value_or()` |
| **Default construction** | Empty optional | Non-OK status with `StatusCode::kUnknown` |

Because `absl::optional` in this repository is merely a thin alias for `std::optional` (defined in [`absl/types/optional.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/types/optional.h)), migrating to `absl::StatusOr` adds **zero runtime overhead** while giving you expressive error diagnostics and integration with Abseil’s logging utilities.

## Step-by-Step Migration Guide

### 1. Update Headers and Return Types

Replace `<optional>` or [`absl/types/optional.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/types/optional.h) with the StatusOr headers, then change your function signature.

```cpp
// Old
#include <optional>
std::optional<Foo> ParseFoo(const std::string& s);

// New
#include "absl/status/statusor.h"
#include "absl/status/status.h"
absl::StatusOr<Foo> ParseFoo(const std::string& s);

```

### 2. Modify Function Implementation

Return success values directly through implicit conversion. Return errors using `absl::Status` factory functions.

```cpp
absl::StatusOr<Foo> ParseFoo(const std::string& s) {
  if (s.empty()) {
    return absl::InvalidArgumentError("empty input string");
  }
  
  // Success path - implicit conversion to StatusOr<T>
  return Foo(s);
}

```

### 3. Update Caller Code

Replace `has_value()` checks with `ok()` calls. Access the value using `operator*` or `operator->` (recommended), or `value()` if you want exception guarantees.

```cpp
// Old optional approach
if (auto opt = ParseFoo(s); opt) {
  opt->DoSomething();
}

// New StatusOr approach
absl::StatusOr<Foo> result = ParseFoo(s);
if (result.ok()) {
  result->DoSomething();  // Use -> or *
} else {
  LOG(ERROR) << result.status();  // Rich error logging
}

```

For fallback values, use `value_or()`:

```cpp
Foo foo = result.value_or(Foo::Default());

```

## Practical Code Examples

### Simple Conversion with Error Handling

This example demonstrates the basic pattern for parsing that might fail:

```cpp
#include "absl/status/statusor.h"
#include "absl/status/status.h"

absl::StatusOr<int> ParseInt(const std::string& s) {
  if (s.empty()) return absl::InvalidArgumentError("empty string");
  try {
    return std::stoi(s);  // Implicitly converts to StatusOr<int>
  } catch (const std::exception&) {
    return absl::InvalidArgumentError("not a number");
  }
}

// Caller implementation
void ProcessValue(const std::string& input) {
  absl::StatusOr<int> maybe = ParseInt(input);
  if (maybe.ok()) {
    int v = *maybe;  // Dereference like a pointer
    Consume(v);
  } else {
    LOG(ERROR) << maybe.status();  // Access the error status
  }
}

```

### Using value_or for Default Values

When you need a default value rather than error handling:

```cpp
absl::StatusOr<std::string> ReadConfig(const std::string& path) {
  if (!FileExists(path)) {
    return absl::NotFoundError("config file missing");
  }
  return ReadFileContents(path);
}

// Caller with fallback
std::string config = ReadConfig("/etc/app.cfg")
                         .value_or("default_config");

```

### Propagating Errors with ASSIGN_OR_RETURN

For chained operations, use Abseil’s `ASSIGN_OR_RETURN` macro to automatically return errors up the call stack:

```cpp
absl::StatusOr<std::string> LoadFile(const std::string& name);
absl::StatusOr<int> ParseHeader(const std::string& data);

absl::StatusOr<User> BuildUser(const std::string& filename) {
  // If LoadFile fails, returns the error immediately
  ASSIGN_OR_RETURN(std::string data, LoadFile(filename));
  ASSIGN_OR_RETURN(int version, ParseHeader(data));
  
  return User{data, version};
}

```

### Working with Move-Only Types

`absl::StatusOr<T>` fully supports move-only types like `std::unique_ptr`:

```cpp
absl::StatusOr<std::unique_ptr<Foo>> MakeFoo() {
  if (!ResourceAvailable()) {
    return absl::UnavailableError("resource exhausted");
  }
  return std::make_unique<Foo>();
}

// Caller
absl::StatusOr<std::unique_ptr<Foo>> foo_or = MakeFoo();
if (foo_or.ok()) {
  foo_or->DoWork();  // Works with unique_ptr
}

```

## Critical Implementation Details

According to the source code in [`absl/status/statusor.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/statusor.h), keep these architectural constraints in mind:

- **Union semantics**: `StatusOr<T>` never holds an OK status; success is signaled exclusively by the presence of `T`.
- **Default construction**: `StatusOr<T>()` creates a **non-OK** object with `StatusCode::kUnknown`, meaning `!ok()` is true initially.
- **Explicit status constructors**: Constructors from a non-OK `absl::Status` are **explicit** to prevent accidental construction from successful statuses.
- **Move safety**: Moving a `StatusOr<T>` that contains an error replaces the source’s status with `kInternal` to guard against use-after-move scenarios.
- **Exception throwing**: Calling `value()` on a non-OK `StatusOr` throws `absl::BadStatusOrAccess`, mirroring `std::bad_optional_access`.

The implementation in `absl/status/statusor.cc` and comprehensive tests in `absl/status/statusor_test.cc` verify these guarantees across all supported types.

## Summary

- **Replace** `std::optional<T>` return types with `absl::StatusOr<T>` to enable rich error reporting.
- **Include** [`absl/status/statusor.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/statusor.h) and [`absl/status/status.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/status.h) in files performing the migration.
- **Return** success values directly; the class handles implicit conversion from `T` to `StatusOr<T>`.
- **Return** errors using `absl::Status` factories like `absl::InvalidArgumentError()` or `absl::NotFoundError()`.
- **Check** `ok()` before accessing values, then use `operator*` or `operator->` for pointer-like semantics.
- **Use** `value_or()` to provide default values when errors occur.
- **Reference** [`absl/status/statusor.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/statusor.h) for the complete API and `absl/status/statusor_test.cc` for usage patterns.

## Frequently Asked Questions

### What is the difference between absl::StatusOr and std::optional?

`std::optional<T>` only tracks whether a value is present or absent, providing no information about why a value might be missing. `absl::StatusOr<T>` carries either a value `T` **or** an `absl::Status` object containing an error code and human-readable message. This makes `StatusOr` suitable for functions that can fail, while `optional` is best for values that may simply be missing for legitimate reasons.

### How do I construct an error StatusOr?

Return any `absl::Status` object directly from your function. Abseil provides factory functions like `absl::InvalidArgumentError("message")`, `absl::NotFoundError("message")`, and `absl::InternalError("message")`. The constructor from `absl::Status` to `StatusOr<T>` is explicit to prevent accidental construction from OK statuses.

### What exception does StatusOr throw on invalid access?

Calling `value()` on a non-OK `StatusOr` throws `absl::BadStatusOrAccess`, which is defined in [`absl/status/statusor.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/status/statusor.h). This mirrors the behavior of `std::optional::value()` throwing `std::bad_optional_access`. To avoid exceptions, check `ok()` first or use `value_or()` with a default.

### Can StatusOr hold move-only types like std::unique_ptr?

Yes, `absl::StatusOr<T>` fully supports move-only types. The implementation properly handles move semantics, allowing you to return `std::unique_ptr` or other move-only resources. When moving a `StatusOr` containing an error, the source's status is replaced with `kInternal` to prevent reuse after move, as implemented in `absl/status/statusor.cc`.