将枚举变体发送给要构造的宏
Sending which enum variant to for a macro to construct
目前,我正在做一个项目,我使用了几个 return 不同错误的板条箱。我试图不对结果使用解包,而是使用问号语法向上传递错误。
为了能够做到这一点,我创建了自己的错误枚举,其中包含来自我正在使用的不同板条箱的不同类型错误的变体,然后使用 map_err 将错误映射到我的错误枚举。我还决定我应该在我重新映射错误的地方添加行和文件,这样我就可以看到我遇到错误的地方。
我的错误枚举
#[derive(Debug, Clone)]
pub enum Error {
GameError(ggez::GameError, String),
RonError(ron::error::Error, String),
}
映射错误的示例
let rdr = filesystem::open(ctx, gen_conf_path)
.map_err(|e| {
Error::GameError(e, format!("at line {} in {}", line!(), file!()))
})?;
问题是我需要发送到 map_err
的闭包变得很长,而且每次都基本相同,除了我要映射到的枚举变体。所以我想创建一个宏来为正确的枚举变体生成闭包。所以我可以写类似下面的东西。
let rdr = filesystem::open(ctx, gen_conf_path).map_err(map_to!(Error::GameError))?
map_to!
宏会生成我之前的代码。
这可能吗?我可以将枚举变体发送到宏并让它构造它,还是应该以完全不同的方式执行此操作?
关于枚举变体的一个有趣的实现细节是初始化器实际上是函数。
We have another useful pattern that exploits an implementation detail of tuple structs and tuple-struct enum variants. These types use ()
as initializer syntax, which looks like a function call. The initializers are actually implemented as functions returning an instance that’s constructed from their arguments. We can use these initializer functions as function pointers that implement the closure traits, which means we can specify the initializer functions as arguments for methods that take closures
– Advanced Functions and Closures - The Rust Programming Language
这意味着,如果您有一个 enum FooBar
,它有一个变体 Foo(i32, i32)
,那么您可以使用并传递 FooBar::Foo
作为 Fn(i32, i32) -> FooBar
。
enum FooBar {
Foo(i32, i32),
Bar(String),
}
fn foo(f: fn(i32, i32) -> FooBar) -> FooBar {
f(1, 2)
}
fn bar<F>(f: F) -> FooBar
where
F: Fn(i32, i32) -> FooBar,
{
f(1, 2)
}
fn main() {
foo(FooBar::Foo);
bar(FooBar::Foo);
}
因此,如果您将枚举变体视为函数,那么您的宏就会变得非常简单:
macro_rules! map_to {
($f:expr) => {
|e| {
$f(e, format!("at line {} in {}", line!(), file!()))
}
};
}
这当然是假设 e
始终是有效类型,相对于 map_to
.
使用的枚举变体
我想为您一开始遇到的问题建议一条不同的路径。所有具有 相同 用途(错误位置)的字段的枚举变体建议您应该 将其拆分为一个结构。(例如, std::io::Error
有一个 struct Error
和一个 enum ErrorKind
。)除了概念上的重复数据删除,这还意味着:
- 错误的接收者不必
match
所有变体即可找到共同信息。 (尽管您可以向枚举添加方法来处理这个问题。)
- 更容易更改位置信息的格式,或者添加更多类似 backtrace 的内容(尚未在稳定的 Rust 中,但我们希望如此)。
- 如下所述,您可以不必明确指定变体。
#[derive(Debug, Clone)]
struct LocatedError {
kind: AnyError,
location: String,
}
#[derive(Debug, Clone)]
enum AnyError {
GameError(ggez::GameError),
RonError(ron::error::Error),
}
碰巧我们把它变成了两个嵌套的独立错误类型。这很有用,因为我们现在可以以通常推荐的方式使用 From
实现来转换为 AnyError
:
impl From<ggez::GameError> for AnyError {
fn from(error: ggez::GameError) -> Self {
Self::GameError(error)
}
}
// And the same for RonError
(顺便说一下,上面的 From
实现可以通过 thiserror
derive macro crate 为你生成。)
现在我们已经处理了转换,宏可以主要关注位置生成,而不是错误类型(因为特征实现选择处理):
impl LocatedError {
fn new(error: impl Into<AnyError>, location: String) -> Self {
Self {
kind: error.into(),
location,
}
}
}
macro_rules! err_here {
() => {
|e| {
LocatedError::new(e, format!("at line {} in {}", line!(), file!()))
}
};
}
在您的示例代码中使用此版本:
let rdr = filesystem::open(ctx, gen_conf_path).map_err(err_here!())?;
请注意,我们不必将枚举变体传递给宏。
目前,我正在做一个项目,我使用了几个 return 不同错误的板条箱。我试图不对结果使用解包,而是使用问号语法向上传递错误。 为了能够做到这一点,我创建了自己的错误枚举,其中包含来自我正在使用的不同板条箱的不同类型错误的变体,然后使用 map_err 将错误映射到我的错误枚举。我还决定我应该在我重新映射错误的地方添加行和文件,这样我就可以看到我遇到错误的地方。
我的错误枚举#[derive(Debug, Clone)]
pub enum Error {
GameError(ggez::GameError, String),
RonError(ron::error::Error, String),
}
映射错误的示例
let rdr = filesystem::open(ctx, gen_conf_path)
.map_err(|e| {
Error::GameError(e, format!("at line {} in {}", line!(), file!()))
})?;
问题是我需要发送到 map_err
的闭包变得很长,而且每次都基本相同,除了我要映射到的枚举变体。所以我想创建一个宏来为正确的枚举变体生成闭包。所以我可以写类似下面的东西。
let rdr = filesystem::open(ctx, gen_conf_path).map_err(map_to!(Error::GameError))?
map_to!
宏会生成我之前的代码。
这可能吗?我可以将枚举变体发送到宏并让它构造它,还是应该以完全不同的方式执行此操作?
关于枚举变体的一个有趣的实现细节是初始化器实际上是函数。
We have another useful pattern that exploits an implementation detail of tuple structs and tuple-struct enum variants. These types use
()
as initializer syntax, which looks like a function call. The initializers are actually implemented as functions returning an instance that’s constructed from their arguments. We can use these initializer functions as function pointers that implement the closure traits, which means we can specify the initializer functions as arguments for methods that take closures– Advanced Functions and Closures - The Rust Programming Language
这意味着,如果您有一个 enum FooBar
,它有一个变体 Foo(i32, i32)
,那么您可以使用并传递 FooBar::Foo
作为 Fn(i32, i32) -> FooBar
。
enum FooBar {
Foo(i32, i32),
Bar(String),
}
fn foo(f: fn(i32, i32) -> FooBar) -> FooBar {
f(1, 2)
}
fn bar<F>(f: F) -> FooBar
where
F: Fn(i32, i32) -> FooBar,
{
f(1, 2)
}
fn main() {
foo(FooBar::Foo);
bar(FooBar::Foo);
}
因此,如果您将枚举变体视为函数,那么您的宏就会变得非常简单:
macro_rules! map_to {
($f:expr) => {
|e| {
$f(e, format!("at line {} in {}", line!(), file!()))
}
};
}
这当然是假设 e
始终是有效类型,相对于 map_to
.
我想为您一开始遇到的问题建议一条不同的路径。所有具有 相同 用途(错误位置)的字段的枚举变体建议您应该 将其拆分为一个结构。(例如, std::io::Error
有一个 struct Error
和一个 enum ErrorKind
。)除了概念上的重复数据删除,这还意味着:
- 错误的接收者不必
match
所有变体即可找到共同信息。 (尽管您可以向枚举添加方法来处理这个问题。) - 更容易更改位置信息的格式,或者添加更多类似 backtrace 的内容(尚未在稳定的 Rust 中,但我们希望如此)。
- 如下所述,您可以不必明确指定变体。
#[derive(Debug, Clone)]
struct LocatedError {
kind: AnyError,
location: String,
}
#[derive(Debug, Clone)]
enum AnyError {
GameError(ggez::GameError),
RonError(ron::error::Error),
}
碰巧我们把它变成了两个嵌套的独立错误类型。这很有用,因为我们现在可以以通常推荐的方式使用 From
实现来转换为 AnyError
:
impl From<ggez::GameError> for AnyError {
fn from(error: ggez::GameError) -> Self {
Self::GameError(error)
}
}
// And the same for RonError
(顺便说一下,上面的 From
实现可以通过 thiserror
derive macro crate 为你生成。)
现在我们已经处理了转换,宏可以主要关注位置生成,而不是错误类型(因为特征实现选择处理):
impl LocatedError {
fn new(error: impl Into<AnyError>, location: String) -> Self {
Self {
kind: error.into(),
location,
}
}
}
macro_rules! err_here {
() => {
|e| {
LocatedError::new(e, format!("at line {} in {}", line!(), file!()))
}
};
}
在您的示例代码中使用此版本:
let rdr = filesystem::open(ctx, gen_conf_path).map_err(err_here!())?;
请注意,我们不必将枚举变体传递给宏。