refactor: add Rust infrastructure: Option, Result and panic

This commit is contained in:
2025-06-23 16:22:55 +08:00
parent 28ff7008a8
commit 3abd0969c0
10 changed files with 297 additions and 11 deletions

23
src/yycc/rust/option.hpp Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <optional>
/// @brief The reproduction of Rust Option type.
/// @details
/// This namespace reproduce Rust Option type, and its members Some and None in C++.
/// However Option is not important than Result, so its implementation is very casual.
namespace yycc::rust::option {
template<typename T>
using Option = std::optional<T>;
template<typename OptionType, typename... Args>
OptionType Some(Args &&... args) {
return OptionType(std::in_place, std::forward<Args>(args)...);
}
template<typename OptionType>
OptionType None() {
return OptionType(std::nullopt);
}
}

43
src/yycc/rust/panic.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "panic.hpp"
#include "../macro/feature_probe.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#if defined(YYCC_CPPFEAT_STACKTRACE)
#include <stacktrace>
#endif
namespace yycc::rust::panic {
void panic(const char* file, int line, const std::string_view& msg) {
// Output message in stderr.
auto& dst = std::cerr;
// TODO: Fix colorful output when finishing `termcolor` lib.
// Print error message if we support it.
// // Setup color
// dst << FOREGROUND<Color::Red>;
// File name and line number message
dst << "program paniked at " << std::quoted(file) << ":Ln" << line << std::endl;
// User custom message
dst << "note: " << msg << std::endl;
// Stacktrace message if we support it.
#if defined(YYCC_CPPFEAT_STACKTRACE)
dst << "stacktrace: " << std::endl;
dst << std::stacktrace::current() << std::endl;
#else
dst << "there is no stacktrace because your C++ runtime do not support it." << std::endl;
#endif
// // Restore color
// dst << RESET;
// Make sure all messages are flushed into screen.
dst.flush();
// Force exit
std::abort();
}
} // namespace yycc::rust::panic

59
src/yycc/rust/panic.hpp Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#include "../macro/feature_probe.hpp"
#include <string_view>
#if defined(YYCC_CPPFEAT_FORMAT)
#include <format>
#endif
/// @brief
namespace yycc::rust::panic {
// TODO: Move these comments into Doxygen comments of this namespace.
// YYC MARK:
// 在使用Rust编写程序后我深刻地认识到将错误立即进行处理的必要性。
// 而在陷入不可恢复错误后,也应当立即退出程序并报告错误,这是对程序健壮性的保证。
// 因此我引入了这个命名空间并带来了与Rust中panic!宏等效的宏和函数。
// YYC MARK:
// 遗憾的是,我无法改变标准库中的异常机制。
// 标准库中会抛出异常的内容仍然会抛出异常,我不能阻止它们。
// 因此我在这里规定在Stardust项目中任何C++异常都应立即被视为错误,并导致程序崩溃退出。
// 也正因为于此,规定不允许注册任何未处理错误的回调,以防止意料之外的继续运行。
// 而对于我们自己编写的,能控制的代码,则应用本文件中提供的宏来代替异常抛出。
// 这样一来,我们代码中的非预期行为会使得程序立即退出,并输出错误信息和堆栈。
// 而标准库中的异常,也会使得程序退出,只不过没有堆栈信息罢了。
/**
* @brief 像Rust的panic!宏一样立即使整个程序崩溃。
* @details 宏参数为需要附加的提示信息。
*/
#define RS_PANIC(msg) ::yycc::rust::panic::panic(__FILE__, __LINE__, (msg))
/**
* @brief 像Rust的panic!宏一样立即使整个程序崩溃。
* @details
* 宏参数为需要格式化的信息和参数格式和参数请参考std::format。
* 因为本质上这个宏就是调用std::format来实现的。
*/
#if defined(YYCC_CPPFEAT_FORMAT)
#if defined(YYCC_CPPFEAT_VA_OPT)
#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg __VA_OPT__(, ) __VA_ARGS__))
#else
#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg, ##__VA_ARGS__))
#endif
#endif
/**
* @brief 像Rust的panic!宏一样立即使整个程序崩溃。
* @details
* 此函数是宏所调用的真正的崩溃输出函数。
* 崩溃信息将被写入到stderr中信息中还包含堆栈信息。
* @param[in] file Panic时的源代码文件。该参数一般由宏代为填写。
* @param[in] line Panic时的源代码文件中的行号。该参数一般由宏代为填写。
* @param[in] msg Panic时需要显示的提示信息。
*/
[[noreturn]] void panic(const char* file, int line, const std::string_view& msg);
} // namespace yycc::rust::panic

View File

@ -0,0 +1,24 @@
#pragma once
#include "../macro/feature_probe.hpp"
#include "../string/parse.hpp"
#include "panic.hpp"
#include "result.hpp"
#define NS_YYCC_STRING_PARSE ::yycc::string::parse
namespace yycc::rust::parse {
#if defined(YYCC_CPPFEAT_EXPECTED)
using Error = NS_YYCC_STRING_PARSE::ParseError;
// template<typename T>
// using Result = std::expected<T, Error>;
#endif
}
#undef NS_YYCC_STRING_PARSE

View File

@ -1,5 +1,5 @@
#pragma once
#include <cinttypes>
#include <cstdint>
#include <cstddef>
#include "../string.hpp"

86
src/yycc/rust/result.hpp Normal file
View File

@ -0,0 +1,86 @@
#pragma once
#include "../macro/feature_probe.hpp"
#if defined(YYCC_CPPFEAT_EXPECTED)
#include <expected>
#endif
/// @brief The reproduction of Rust Option type.
/// @details
/// After writing a program in Rust, I deeply realized the advantages of Rust and its indispensable infrastructure, \c Result.
/// Therefore, it is essential to introduce Result into C++ to strengthen the security of the code.
/// I simulated Result in C++ as best I could, and its members Ok and Err (which are actually done by DeepSeek).
///
/// Why not write it the C++ way? Because the way C++ uses Result is too ugly and not explicit enough.
/// In the way C++ is written, the expected value is gotten by returning directly,
/// and if you encounter void specialization, you must write a pair of curly braces, which is very unclear.
/// For unexpected value, you need to manually build it by calling \c std::unexpected, which is more of a pain.
/// If you need to construct an unexpected value in place, you even need to put a \c std::in_place as the first argument to the constructor,
/// Otherwise, the std::unexpected constructor will not forward the given arguments to the underlying constructor.
///
///
/// @remarks
/// This namespace only work with environment supporting `std::expected` (i.e. C++ 23).
namespace yycc::rust::result {
#if defined(YYCC_CPPFEAT_EXPECTED)
/**
* @brief Equivalent Rust \c Result in C++
*/
template<typename T, typename E>
using Result = std::expected<T, E>;
/**
* @brief Equvialent Rust \c Result::Ok in C++
* @param[in] args The arguments for building expected value.
* @return An built Result instance with expected value.
*/
template<typename ResultType, typename... Args>
ResultType Ok(Args &&...args) {
using T = ResultType::value_type;
if constexpr (!std::is_void_v<T>) {
return ResultType(std::in_place, std::forward<Args>(args)...);
} else {
static_assert(sizeof...(Args) == 0, "Ok<void> cannot accept arguments");
return ResultType(std::in_place);
}
}
/**
* @brief Equvialent Rust \c Result::Err in C++
* @param[in] args The arguments for building unexpected value.
* @return An built Result instance with unexpected value.
*/
template<typename ResultType, typename... Args>
ResultType Err(Args &&...args) {
return ResultType(std::unexpect, std::forward<Args>(args)...);
}
// TODO: Move these comments into Doxygen comments of this namespace.
// YYC MARK:
// Result类型中类型E可以是根据你需求的任意值。
// 在Rust中一个非期望值类型Ea可以被转换为另一个非期望值类型Eb。
// 这一特性通过From trait来实现。这样你就可以在一个函数中安全地将一种非期望值包装成另一种非期望值。
// 但在C++中我们有C++的方式来做同样的事。
// 假设对于每一个类型E我们都分别定义一个struct来描述它们
// 那么我们只需要为struct添加一些额外的构造函数就可以将它们从一个类型转换到另一个。
// ---
// 例如类型Ea是一个名为IoError的struct。
// 在这个struct中有一个类型为IoErrorKind的成员指示了该IO错误的类别。
// 与此同时,它还有一个以自己类型为唯一参数的构造函数,用于构建(复制或移动)它自己。
// 现在在一个函数中。我们希望将它转换为另一个名为SystemError的类型Eb。
// 你需要做的仅仅是新建一个名为SystemError的struct然后为它编写所有必要的构造函数和其它函数。
// 然后,重点是,为它添加一个形参类型为`const IoError&`的构造函数。
// 这样一来,我们就可以通过类似这样的调用:`Err<Result<T, E>>(result.error());`简单地将类型Ea转换为类型Eb。
// YYC MARK:
// 在Rust中如果你想获得非预期值的人类可读说明你就必须要实现名为Display的trait。
// 而你不需要在C++中这样做,你必须编写自己的转换函数,以适应各种输出需求。
// 例如在使用std::format时你需要编写适合std::format的格式化适配器。
// 又如你在使用std::cerr的operator<<重载时,你也需要编写合适的适配器。
#endif
}