Skip to content

Commit 349a1f9

Browse files
committed
add result tutorial
1 parent 192d33f commit 349a1f9

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

docs/tutorials/result.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Tutorial: Using the `Result` Class
2+
3+
This tutorial will guide you through using the `Result` class template, a powerful error-handling mechanism inspired by `std::expected` that allows you to handle both success values and errors in a type-safe manner. Unlike `std::expected` however, the expected type is *always* valid.
4+
5+
## Table of Contents
6+
1. [Key Concepts](#key-concepts)
7+
2. [Defining Error Types](#defining-error-types)
8+
3. [Using `Result` with Value Types](#using-result-with-value-types)
9+
4. [Using `Result<void>`](#using-resultvoid)
10+
5. [Monadic Operations](#monadic-operations)
11+
6. [Comparison Operators](#comparison-operators)
12+
7. [Important Notes](#important-notes)
13+
14+
---
15+
16+
## 1. Key Concepts<a name="key-concepts"></a>
17+
The `Result` class:
18+
- Always holds a **value** (success), but may sometimes contain an **error**
19+
- Can convert implicitly to the value type
20+
- Can have multiple possible error types, but only one error value
21+
- Uses a `sentinel_v<T>` for the value when an error occurs
22+
- Provides monadic operations for functional-style error handling, similar to those of std::expected
23+
24+
## 2. Defining Error Types<a name="defining-error-types"></a>
25+
Define your error types by inheriting from `ResultError`:
26+
27+
```cpp
28+
struct FileNotFound : protected zest::ResultError {};
29+
struct PermissionDenied : protected zest::ResultError {};
30+
struct InvalidFormat : protected zest::ResultError {};
31+
```
32+
33+
## 3. Using `Result` with Value Types<a name="using-result-with-value-types"></a>
34+
### Basic Usage
35+
```cpp
36+
zest::Result<std::string, FileNotFound, PermissionDenied> read_file() {
37+
if (!file_exists()) return FileNotFound{};
38+
if (!has_permission()) return PermissionDenied{};
39+
return "todo"; // (success) return the contents of the file
40+
}
41+
42+
// Using the result
43+
auto result = read_file();
44+
if (result.has_error()) {
45+
// Handle error
46+
if (auto err = result.get_error<FileNotFound>()) {
47+
std::println("File not found");
48+
}
49+
} else {
50+
std::println("value: {}", result); // result converted to std::string implicitly
51+
}
52+
```
53+
54+
### Extracting Values
55+
```cpp
56+
// Result can implicitly convert to its value type
57+
int value = Result<int, ExampleError>(2);
58+
std::println("{}", value); // output: 2
59+
60+
// Result::or_default can be used to change the default error value
61+
int value = Result<int, ExampleError>(ExampleError{}).or_default(-4);
62+
std::println("{}", value); // output: -4
63+
64+
// Result::or_default only changes the value if there's an error
65+
int value = Result<int, ExampleError>(5).or_default(-4);
66+
std::println("{}", value); // output: 5
67+
```
68+
69+
## 4. Using `Result<void>`<a name="using-resultvoid"></a>
70+
For operations without return values:
71+
72+
```cpp
73+
zest::Result<void, PermissionDenied> delete_file() {
74+
if (!has_permission()) return PermissionDenied{};
75+
// Perform deletion
76+
return {}; // Success
77+
}
78+
79+
// Usage
80+
auto delete_result = delete_file();
81+
if (delete_result.has_error()) {
82+
// Handle error...
83+
}
84+
```
85+
86+
## 5. Monadic Operations<a name="monadic-operations"></a>
87+
88+
Monadic operations can be used for functional-style error handling.
89+
90+
### Chaining Operations with `and_then`
91+
92+
Invokes the callable with the value if there is no error. The callable must return a `Result` which can contain any of the possible error types.
93+
94+
```cpp
95+
read_file()
96+
.and_then([](std::string contents) -> Result<Data, FileNotFound, PermissionDenied> {
97+
return parse(contents); // parse returns Data
98+
});
99+
```
100+
101+
### Error Handling with `or_else`
102+
103+
Invokes the callable with the error if there is an error. The callable must return a `Result` with the same value type.
104+
105+
```cpp
106+
read_file()
107+
.or_else([](auto error) -> zest::Result<int, FileNotFound, PermissionDenied> {
108+
// print error
109+
std::println("An error occurred: {}", error);
110+
// Fallback operation
111+
return read_backup_file();
112+
});
113+
```
114+
115+
### Inspecting Errors
116+
117+
Invokes the callable with the error if there is an error. The callable must return `void`.
118+
119+
```cpp
120+
result.inspect_error([](const auto& error) {
121+
// Log error without changing result
122+
std::cerr << "Operation failed: ";
123+
if constexpr (std::is_same_v<decltype(error), FileNotFound>) {
124+
std::cerr << "File not found";
125+
}
126+
// ...
127+
});
128+
```
129+
130+
### Transforming Values with `transform`
131+
132+
For non-void `Result` instances, `transform` can be used to transform a value if there is no error.
133+
134+
```cpp
135+
auto modified = result.transform([](int res) {
136+
res.value += 10; // Modify value in-place
137+
return res;
138+
});
139+
```
140+
141+
## 6. Comparison Operators<a name="comparison-operators"></a>
142+
Results can be compared if their value types are comparable:
143+
144+
```cpp
145+
auto result1 = read_file();
146+
auto result2 = read_another_file();
147+
148+
if (result1 == result2) {
149+
// Compares the contained values
150+
}
151+
```
152+
153+
## 7. Important Notes<a name="important-notes"></a>
154+
1. **Sentinel Values**:
155+
- The value type `T` must have a defined sentinel value (`sentinel_v<T>`)
156+
- The sentinel value for built-in number types is the maximum value for the given type, or infinity if the type supports it.
157+
- When an error occurs, the value is set to this sentinel
158+
159+
2. **Error Type Requirements**:
160+
- All error types must inherit from `ResultError`
161+
- Error types in a single `Result` must be unique (can't do `Result<void, ExampleError, ExampleError>`)
162+
163+
3. **Monadic Constraints**:
164+
- `and_then` requires callables to return compatible `Result` types
165+
- `or_else` requires callables to handle all possible error types
166+
- Constraints are checked at compile time
167+
168+
4. **Void Specialization**:
169+
- Use `Result<void, Errs...>` for operations without return values
170+
- `and_then` callables take no arguments for void results

0 commit comments

Comments
 (0)