前言
Rust 的 Result
类型是一种优雅的错误处理机制,它强制开发者显式处理成功和失败两种状态,避免了未检查异常带来的不确定性。在 C++中,虽然可以通过异常或错误码实现类似功能,但 Result
类型提供了一种更结构化的方式,尤其适用于需要明确错误路径的项目。
本文介绍一个基于 C++的 Result
实现,其设计灵感来自 Rust。
Rust 的 Result 类型分析
在 Rust 语言中,Result
是一种非常重要的类型,用于表示函数执行的结果,它允许开发者优雅地处理成功和失败的情况,同时避免了传统编程语言中常见的错误处理问题。
它是一个带有变体的枚举,Ok(T)
表示成功并包含一个值,而 Err(E)
表示错误并包含一个错误值。
1
2
3
4
| enum Result<T, E> {
Ok(T),
Err(E),
}
|
在 Rust 中,通常这样使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(a / b)
}
}
fn main() {
let result = divide(10, 2);
match result {
Ok(value) => println!("Result: {}", value),
Err(err) => println!("Error: {}", err),
}
}
|
除了使用 match
匹配,还有几个主要的方法:
1
2
| let result = divide(10, 2);
println!("Result: {}", result.unwrap()); // 输出:Result: 5
|
unwrap()
方法:如果 Result
是 Ok
,则返回内部的值;如果是 Err
,则会触发 panic。
1
2
| let result = divide(10, 0);
println!("Result: {}", result.unwrap_or(-1)); // 输出:Result: -1
|
unwrap_or()
方法:如果 Result
是 Ok
,则返回内部的值;如果是 Err
,则返回一个默认值。
C++的 Rust 实现
Rust 中的 Result 的 Ok(T)
和 Err(E)
是一个范型,最初的实现尝试使用 std::variant<T, E>
存储结果,但遇到了一个关键问题:当 T
和 E
类型相同时,std::variant
无法区分成功和错误值。例如,对于 Result<int, int>
,std::variant<int, int>
的两个备选项类型相同,导致无法明确赋值来源。为此,改用 std::any
存储值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
| // Result type, similar to Rust Result
#pragma once
#include <any>
#include <iostream>
#include <stdexcept>
#include <string>
// custom exception(optional)
class ResultException final : public std::runtime_error {
public:
explicit ResultException(const std::string& message) : std::runtime_error(message) {}
};
template <typename T, typename E = std::string>
class Result {
private:
// Using variant is actually better, but when T and E are of the same type, it can cause ambiguity.
// std::variant<T, E> data;
std::any ok_;
std::any err_;
public:
// Construct a successful Result
static Result Ok(T value) {
Result result;
result.ok_ = std::move(value);
return result;
}
// Construct a wrong Result
static Result Err(E error) {
Result result;
result.err_ = std::move(error);
return result;
}
// Check if successful
[[nodiscard]] bool is_ok() const { return ok_.has_value(); }
// Check if error
[[nodiscard]] bool is_err() const { return err_.has_value(); }
// Get the successful value and throw an exception if it is an error
T unwrap() const {
if (is_err()) {
throw ResultException("Unwrapped an Err value");
}
return std::any_cast<T>(ok_);
}
// Get the error value and throw an exception if successful
E unwrap_err() const {
if (is_ok()) {
throw ResultException("Unwrapped_err on an Ok value");
}
return std::any_cast<E>(err_);
}
// Get values safely, provide default values
T unwrap_or(T default_value) const { return is_ok() ? std::any_cast<T>(ok_) : default_value; }
// Pattern matching style processing
template <typename OkFunc, typename ErrFunc>
auto match(OkFunc ok_func, ErrFunc err_func) const {
if (is_ok()) {
return ok_func(std::any_cast<T>(ok_));
} else {
return err_func(std::any_cast<E>(err_));
}
}
};
|
这里实现一个简洁版本,只保留基本功能,足够简单,不会出错。
实现解析
核心结构:
1
2
3
4
5
6
7
| template <typename T, typename E = std::string>
class Result {
private:
std::any ok_;
std::any err_;
// ...
};
|
ok_
和 err_
分别存储成功值和错误值,通过 has_value()
判断状态。
关键方法:
构造方法
1
2
| static Result Ok(T value); // 构造成功结果
static Result Err(E error); // 构造错误结果
|
状态检查
1
2
| [[nodiscard]] bool is_ok() const;
[[nodiscard]] bool is_err() const;
|
值提取
unwrap()
:返回成功值,若为错误则抛出 ResultException
。unwrap_err()
:返回错误值,若为成功则抛出异常。unwrap_or(T default_value)
:安全提取值,提供默认回退。
模式匹配
1
2
| template <typename OkFunc, typename ErrFunc>
auto match(OkFunc ok_func, ErrFunc err_func) const;
|
根据状态调用对应的函数,类似于 Rust 的 match
语法。
用法
用法也很简单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| #include <print> // C++ 23
Result<int, std::string> divide(int numerator, int denominator) {
if (denominator == 0) {
return Result<int, std::string>::Err("Division by zero");
}
return Result<int, std::string>::Ok(numerator / denominator);
}
int main() {
// success
auto success_result = divide(10, 2);
success_result.match(
[](int value) { std::println("Success: {}", value); },
[](const std::string& err) { std::println("Error: {}", err); }
);
std::println("is_ok:{} is_err:{} unwrap:{}", success_result.is_ok(), success_result.is_err(),
success_result.unwrap());
// wrong
auto error_result = divide(10, 0);
error_result.match(
[](int value) { std::println("Success: {}", value); },
[](const std::string& err) { std::println("Error: {}", err); }
);
std::println("is_ok:{} is_err:{} unwrap_err:{}", error_result.is_ok(), error_result.is_err(),
error_result.unwrap_err());
// methond 2
if(auto result = divide(10, 2); re.is_ok()) {
std::println("Success: {}", re.unwrap());
}
else {
std::println("Error: {}", re.unwrap_err());
}
return 0;
}
|
总结
Rust 的 Result
是一个不错的类型,可以有效解决返回值的问题,能够携带正确值或者错误信息。这里通过 std::any
实现的 Result
类型提供了一种灵活的错误处理机制,尤其适用于 T
和 E
类型可能相同的场景。
对于 Result
的 C++实现,有很多方式,也有开源实现,比如: https://github.com/oktal/result
https://zh.cppreference.com/w/cpp/utility/variant
https://zh.cppreference.com/w/cpp/utility/any
https://github.com/oktal/result