6个稳定版本
2.0.2 | 2023年10月3日 |
---|---|
2.0.1 | 2023年2月16日 |
2.0.0 | 2022年9月7日 |
1.0.2 | 2022年9月1日 |
1.0.0 | 2022年8月31日 |
#180 in 构建工具
105KB
2K SLoC
EnPow
EnPow是一个过程宏crate,用于增强用户定义枚举,使其具有通常从标准库中的Result<T, E>
和Option<T>
中已知的方法。它可以生成像fn is_<variant>(&self) -> bool
或fn unwrap_<variant>(self) -> <inner>
这样的方法,支持具有命名或未命名的字段(或没有字段),以及泛型。有关支持的具体方法,请参阅enpow
宏文档。
此外,这个crate允许将每个枚举变体的相关数据提取到单独的结构体中,这可以在设计抽象语法树时使代码更加紧凑。有关详细信息,请参阅extract
宏文档。
也可以将这两个宏按正确顺序组合使用:首先是 extract
,然后是 enpow
。组合这两个宏可以避免为 Ref
或 Mut
结构体变体生成单独的结构体,如以下用例演示中进一步解释的那样。不过,这两个宏也可以独立使用。
安装
⚠️ 支持的最小 Rust 工具链版本是 Rust 1.58.0
将以下内容添加到您的 Cargo.toml
中,以便将 enpow
作为依赖项添加到您的项目中。
[dependencies]
enpow = "~2.0.2"
或者,在您的 crate 根目录内运行以下命令。
cargo add enpow
快速入门
Enpow
要为枚举生成所有可用方法,导入 enpow
宏,并在枚举定义之前添加 #[enpow(All)]
。您还可以选择性地生成某些方法。有关更多信息,请参阅以下用例演示或 enpow
宏的文档。
use enpow::enpow;
#[enpow(All)]
pub enum IpAddress {
None,
V4(u8, u8, u8, u8),
V6(String),
Multi {
v4: (u8, u8, u8, u8),
v6: String,
},
}
一些 生成的函数如下
fn v4(self) -> Option<(u8, u8, u8, u8)>
fn is_none(&self) -> bool
fn unwrap_v6(self) ->String
fn expect_multi(self, msg: &str) ->IpAddressMulti
- ...
除了这些方法外,还需要生成以下结构体
pub struct IpAddressMulti {
pub v4: (u8, u8, u8, u8),
pub v6: String,
}
Extract
要提取枚举的所有变体到单独的类型中,导入 extract
宏,并在枚举定义之前添加 #[extract(All)]
。您还可以选择性地提取某些变体。有关更多信息,请参阅以下用例演示或 extract
宏的文档。
use enpow::extract;
#[extract(All)]
pub enum IpAddress {
None,
V4(u8, u8, u8, u8),
V6(String),
Multi {
v4: (u8, u8, u8, u8),
v6: String,
},
}
这将生成以下数据类型
pub enum IpAddress {
None(IpAddressNone),
V4(IpAddressV4),
V6(IpAddressV6),
Multi(IpAddressMulti),
}
pub struct IpAddressNone;
pub struct IpAddressV4(pub u8, pub u8, pub u8, pub u8);
pub struct IpAddressV6(pub String);
pub struct IpAddressMulti {
pub v4: (u8, u8, u8, u8),
pub v6: String,
}
用例
以下代码描述了一个简单的支持不同日志级别的日志系统。然后我们创建一个示例日志并打印其中的所有错误。
/// A log entry
#[derive(Clone)]
pub enum LogEntry<C: ToString + Clone> {
/// A simple note without context
Note(
/// Note's message
String
),
/// A warning with a given context
Warning(
/// Warning's message
String,
/// Context of the warning
C
),
/// An error message with error code and context
Error {
/// Error message
message: String,
/// Context of the error
context: C,
/// Error code
code: i16,
},
}
/// Application log for a certain context type
pub struct Log<C: ToString + Clone> {
/// Log entries
entries: Vec<LogEntry<C>>,
}
impl<C: ToString + Clone> Log<C> {
/// Collects all entries of type `LogEntry::Error` from the log
pub fn get_errors(&self) -> Vec<LogEntry<C>> {
self.entries.iter()
.filter(|entry| match entry {
LogEntry::Error { .. } => true,
_ => false,
})
.cloned()
.collect()
}
}
/// Line number in source
type Line = usize;
// Create a sample log
let log = Log { entries: vec![
LogEntry::Note("All fine 😊".into()),
LogEntry::Warning("There might be an issue here 🤔".into(), 4),
LogEntry::Error {
message: "There _was_ an issue 😖".into(),
context: 4,
code: -1,
},
LogEntry::Error {
message: "Follow up".into(),
context: 12,
code: -7,
},
] };
// Get and print all errors
let errors = log.get_errors();
if !errors.is_empty() {
eprintln!("Failed for the following reasons:");
for error in errors {
match error {
LogEntry::Error { message, context: line, code } => {
eprintln!("Error {code} at {line}: {message}");
}
_ => panic!("Expected to find a LogEntry::Error"),
}
}
}
此代码可以工作,但是每次都需要对每个日志条目的具体变体进行模式匹配,这略显冗长。
使用 enpow
这时,enpow
宏就派上用场了。它可以生成一些辅助方法,可以使我们的代码更加简洁。我们特别使用了 is_<variant>()
(关键字 IsVar
)和 unwrap_<variant>()
(关键字 UnwrapVar
)方法。
use enpow::enpow; // ℹ️
/// A log entry
#[enpow(IsVar, UnwrapVar)] // ℹ️
#[derive(Clone)]
pub enum LogEntry<C: ToString + Clone> {
/// A simple note without context
Note(
/// Note's message
String
),
/// A warning with a given context
Warning(
/// Warning's message
String,
/// Context of the warning
C
),
/// An error message with error code and context
Error {
/// Error message
message: String,
/// Context of the error
context: C,
/// Error code
code: i16,
},
}
/// Application log for a certain context type
pub struct Log<C: ToString + Clone> {
/// Log entries
entries: Vec<LogEntry<C>>,
}
impl<C: ToString + Clone> Log<C> {
/// Collects all entries of type `LogEntry::Error` from the log
pub fn get_errors(&self) -> Vec<LogEntry<C>> {
self.entries.iter()
.filter(|entry| entry.is_error()) // ℹ️
.cloned()
.collect()
}
}
/// Line number in source
type Line = usize;
// Create a sample log
let log = Log { entries: vec![
LogEntry::Note("All fine 😊".into()),
LogEntry::Warning("There might be an issue here 🤔".into(), 4),
LogEntry::Error {
message: "There _was_ an issue 😖".into(),
context: 4,
code: -1,
},
LogEntry::Error {
message: "Follow up".into(),
context: 12,
code: -7,
},
] };
// Get and print all errors
let errors = log.get_errors();
if !errors.is_empty() {
eprintln!("Failed for the following reasons:");
for error in errors {
let error = error.unwrap_error(); // ℹ️
eprintln!("Error {} at {}: {}", error.code, error.context, error.message);
}
}
尽管这段代码已经很简洁了,但仍有一些粗糙的边缘。在收集所有错误时,它们仍然以没有类型系统保证只包含错误的 Vec<LogEntry>
形式返回。我们可以通过从 get_errors()
返回已构造的类型 LogEntryError<C>
来解决这个问题,以帮助 unwrap_error()
方法。
然而,目前代码中有多个(取决于生成的方法,最多可达四个)部分定义了相同的数据:在枚举变体 LogEntry::Error { .. }
中,以及刚才提到的自动生成的结构体 LogEntryError<C>
中。可能更希望将枚举变体提取到这个结构体中,并让变体只指向它,如下所示:LogEntry::Error(LogEntryError<C>)
。这在处理需要引用变体关联数据的方时特别有用,因为到目前为止,需要为这些生成特殊的 LogEntryErrorRef<C>
和 LogEntryErrorMut<C>
结构体。数据提取后,只需使用 &LogEntryError<C>
和 &mut LogEntryError<C>
即可。
使用 extract
在这里,extract
宏派上用场,它会自动为我们完成这项工作。我们告诉宏为每个具有一个以上未命名字段(关键字 Unnamed
)或命名字段(关键字 Named
)的变体进行提取。这将自动将受影响的两个变体改为 LogEntry::Warning(LogEntryWarning<C>)
和 LogEntry::Error(LogEntryError<C>)
。然后我们可以使用生成的类型 LogEntryError<C>
来保证 get_errors()
实际上只返回错误。注意,尽管枚举代码没有手动更改,但日志条目的构建方式也发生了变化。extract
还自动实现了 From
特性,以将提取类型的实例转换为相应的枚举变体。
此外,我们使用方法 <variant>_as_ref()
(关键字 VarAsRef
)来使收集所有错误条目和解包它们更加简洁。为了使自动生成的 LogEntryError<C>
结构体的克隆工作,我们添加了 inner(derive(Clone))
属性。
⚠️ 宏顺序:当结合两个宏时,
enpow
必须放在 afterextract
之后才能正确工作。如果辅助属性inner
放在extract
和enpow
之间,它将只对extract
有效。如果它放在extract
和enpow
之后,它将同时对两个宏有效。此外,正常的derive
宏必须放在 afterextract
之后才能正确工作。
use enpow::{enpow, extract}; // ℹ️
/// A log entry
#[extract(Unnamed, Named)] // ℹ️
#[enpow(VarAsRef)] //
#[inner(derive(Clone))] //
#[derive(Clone)]
pub enum LogEntry<C: ToString + Clone> {
/// A simple note without context
Note(
/// Note's message
String
),
/// A warning with a given context
Warning(
/// Warning's message
String,
/// Context of the warning
C
),
/// An error message with error code and context
Error {
/// Error message
message: String,
/// Context of the error
context: C,
/// Error code
code: i16,
},
}
/// Application log for a certain context type
pub struct Log<C: ToString + Clone> {
/// Log entries
entries: Vec<LogEntry<C>>,
}
impl<C: ToString + Clone> Log<C> {
/// Collects all entries of type `LogEntry::Error` from the log
pub fn get_errors(&self) -> Vec<LogEntryError<C>> { // ℹ️
self.entries.iter()
.filter_map(|entry| entry.error_as_ref()) // ℹ️
.cloned()
.collect()
}
}
/// Line number in source
type Line = usize;
// Create a sample log
let log = Log { entries: vec![
LogEntry::Note("All fine 😊".into()),
LogEntry::from(LogEntryWarning( // ℹ️
"There might be an issue here 🤔".into(),
4,
)),
LogEntry::from(LogEntryError { // ℹ️
message: "There _was_ an issue 😖".into(),
context: 4,
code: -1,
}),
LogEntry::from(LogEntryError { // ℹ️
message: "Follow up".into(),
context: 12,
code: -7,
}),
] };
// Get and print all errors
let errors = log.get_errors();
if !errors.is_empty() {
eprintln!("Failed for the following reasons:");
for error in errors {
// ℹ️
eprintln!("Error {} at {}: {}", error.code, error.context, error.message);
}
}
最后,我们通过定义另一个命名模式来移除生成结构体的长名称。为此,使用具有参数 type_name
或 type_names
的相同 inner()
辅助属性。此外,使用 inner()
,还可以更改变体名称在方法中的显示方式 method_name="..."
,并且可以为单个变体添加自动派生。
use enpow::{enpow, extract};
/// A log entry
#[extract(Unnamed, Named)]
#[enpow(VarAsRef)]
#[inner(type_names=Var, derive(Clone))] // ℹ️
#[derive(Clone)]
pub enum LogEntry<C: ToString + Clone> {
/// A simple note without context
Note(
/// Note's message
String
),
/// A warning with a given context
Warning(
/// Warning's message
String,
/// Context of the warning
C
),
/// An error message with error code and context
Error {
/// Error message
message: String,
/// Context of the error
context: C,
/// Error code
code: i16,
},
}
/// Application log for a certain context type
pub struct Log<C: ToString + Clone> {
/// Log entries
entries: Vec<LogEntry<C>>,
}
impl<C: ToString + Clone> Log<C> {
/// Collects all entries of type `LogEntry::Error` from the log
pub fn get_errors(&self) -> Vec<Error<C>> { // ℹ️
self.entries.iter()
.filter_map(|entry| entry.error_as_ref())
.cloned()
.collect()
}
}
/// Line number in source
type Line = usize;
// Create a sample log
let log = Log { entries: vec![
LogEntry::Note("All fine 😊".into()),
LogEntry::from(Warning( // ℹ️
"There might be an issue here 🤔".into(),
4,
)),
LogEntry::from(Error { // ℹ️
message: "There _was_ an issue 😖".into(),
context: 4,
code: -1,
}),
LogEntry::from(Error { // ℹ️
message: "Follow up".into(),
context: 12,
code: -7,
}),
] };
// Get and print all errors
let errors = log.get_errors();
if !errors.is_empty() {
eprintln!("Failed for the following reasons:");
for error in errors {
eprintln!("Error {} at {}: {}", error.code, error.context, error.message);
}
}
这只是一个快速入门示例,用于理解该crate的使用和用法。有关详细信息,请参阅宏的文档。
灵感和替代方案
虽然这个crate的第一个计划仅限于简单的 unwrap
方法等,但crate variantly
对此想法进行了很好的扩展。它可以看作是这个crate的替代品,具有部分不同的功能集。
另一个具有部分不同功能集的替代方案是 enum_variant_type
。它的主要重点是为每个枚举变体生成单独的类型。
贡献
除非你明确表示,否则你提交的任何旨在包含在作品中的贡献,如Apache-2.0许可中定义的,都应按以下方式双重许可,而无需任何附加条款或条件。
许可证
© 2022 Florian Köhler。
此项目可按你的选择许可
此项目的SPDX许可证标识符为 MIT OR Apache-2.0
。
依赖项
约1.5MB
约35K SLoC