11 个版本
0.3.2 | 2023年8月7日 |
---|---|
0.3.1 | 2023年8月1日 |
0.3.0 | 2023年7月31日 |
0.2.4 | 2022年7月3日 |
0.1.5 | 2022年6月21日 |
#2 in #多态
每月下载量 29 次
65KB
854 行
状态设计模式和其他动态多态通常使用 dyn Trait 对象来解决。
enum-matching 比 Trait 对象更简单、更高效,但在此情况下直接使用它会在接口方法中将状态抽象“涂抹”。
提出的宏 impl_match!{...}
和 #[gen(...)]
提供了两种不同的枚举匹配方式,通过将方法按 enum
变体进行视觉分组,使得在状态设计模式和多态问题中使用枚举匹配更加方便。
impl_match! 宏
这是一个类似于项目宏的宏,它可以封装一个状态 enum
声明和一个或多个 impl
块,允许你在这些 impl
的方法体中编写 match-expressions,而不需要 match-arms,将 match-arms 写入相应的 enum
变体。
使用示例
rust-book 的第 17.3 章“实现面向对象设计模式”展示了 Rust 中状态模式的实现,提供了以下行为
pub fn main() {
let mut post = blog::Post::new();
post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());
post.request_review(); // without request_review() - approve() should not work
post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
通过在 Cargo.toml 中设置
[dependencies]
methods-enum = "0.3.2"
可以这样解决,例如
mod blog {
pub struct Post {
state: State,
content: String,
}
methods_enum::impl_match! {
impl Post {
pub fn add_text(&mut self, text: &str) ~{ match self.state {} }
pub fn request_review(&mut self) ~{ match self.state {} }
pub fn approve(&mut self) ~{ match self.state {} }
pub fn content(&mut self) -> &str ~{ match self.state { "" } }
pub fn new() -> Post {
Post { state: State::Draft, content: String::new() }
}
}
pub enum State {
Draft: add_text(text) { self.content.push_str(text) }
request_review() { self.state = State::PendingReview },
PendingReview: approve() { self.state = State::Published },
Published: content() { &self.content }
}
} // <-- impl_match!
}
这个宏所做的一切就是为所有 enum
变体分支在方法体中标记为 ~
的未完成的 match-expressions 完成匹配
(EnumName)::(Variant) => { match-ARM模块从枚举 声明 }
.
如果一个{}
块(没有=>
)被设置在未完成的匹配表达式末尾,它将被放置在所有没有此方法在enum
中的变体分支中。
(EnumName)::(Variant) => {默认match-ARM模块}
.
因此,您可以看到编译器将接收到的所有代码,但以按设计模式结构化的形式。
rust-analyzer[^rust_analyzer]完美地定义了所有块中的标识符。所有在IDE中的提示、自动完成和替换都在显示在enum
中的match-arm中处理,就像它们在本地match-block中一样。此外,“内联宏”命令在IDE中也可以使用,显示结果代码。
[^rust_analyzer]:在nightly版或旧版rust版本下运行时,rust-analyzer可能无法展开proc-macro。在这种情况下,建议在它的设置中设置:["rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" }
]
其他功能
-
您还可以在宏中包含
impl (Trait) for ...
块。在枚举中指定Trait
的名称(不带路径),在相应的arm-block之前。以下是一个使用Display
的示例。 -
那里还展示了泛型方法的示例:
mark_obj<T: Display>()
。
有关泛型的非关键细微差别,请参阅文档。 -
@
- 在enum
声明之前的字符,例如:@enum Shape {...
禁用了传递到编译器:只有match-arms将被处理。如果此enum
已在代码的其他地方声明,包括宏之外,则可能需要此操作。 -
如果您正在使用带有字段
enum
,则在使用它们的名称之前,指定分解字段的模板(IDE[^rust_analyzer]完全正确地处理此类变量)。接受的分解模板由相同枚举变体的下游方法使用,并且可以重新分配。示例
methods_enum::impl_match! {
enum Shape<'a> {
// Circle(f64, &'a str), // if you uncomment or remove these 4 lines
// Rectangle { width: f64, height: f64 }, // it will work the same
// }
// @enum Shape<'a> {
Circle(f64, &'a str): (radius, mark)
zoom(scale) { Shape::Circle(radius * scale, mark) } // template change
fmt(f) Display { write!(f, "{mark}(R: {radius:.1})") }; (_, mark)
mark_obj(obj) { format!("{} {}", mark, obj) }; (radius, _)
to_rect() { *self = Shape::Rectangle { width: radius * 2., height: radius * 2.,} }
,
Rectangle { width: f64, height: f64}: { width: w, height}
zoom(scale) { Shape::Rectangle { width: w * scale, height: height * scale } }
fmt(f) Display { write!(f, "Rectangle(W: {w:.1}, H: {height:.1})") }; {..}
mark_obj(obj) { format!("⏹️ {}", obj) }
}
impl<'a> Shape<'a> {
fn zoom(&mut self, scale: f64) ~{ *self = match *self }
fn to_rect(&mut self) -> &mut Self ~{ match *self {}; self }
fn mark_obj<T: Display>(&self, obj: &T) -> String ~{ match self }
}
use std::fmt::{Display, Formatter, Result};
impl<'a> Display for Shape<'a>{
fn fmt(&self, f: &mut Formatter<'_>) -> Result ~{ match self }
}
} // <--impl_match!
pub fn main() {
let mut rect = Shape::Rectangle { width: 10., height: 10. };
assert_eq!(format!("{rect}"), "Rectangle(W: 10.0, H: 10.0)");
rect.zoom(3.);
let mut circle = Shape::Circle(15., "⭕");
assert_eq!(circle.mark_obj(&rect.mark_obj(&circle)), "⭕ ⏹️ ⭕(R: 15.0)");
// "Rectangle(W: 30.0, H: 30.0)"
assert_eq!(circle.to_rect().to_string(), rect.to_string());
}
- 调试标志。它们可以放置在宏非常开始处的括号内的空格中,
例如:impl_match! { (ns )
...- 标志
ns
或sn
无论如何 - 如果它们被错误指定,则将替换为编译错误中的枚举变体中方法和特性的语义绑定。 - 标志
!
- 如果它们被错误指定,将导致编译错误,但不会移除语义绑定。
- 标志
链接
gen() 宏
在单个(非 Trait)实现块之前设置宏属性。根据实现块的方法签名,它生成:从参数元组生成参数的 enum
,并生成这些方法的 {}
主体,通过调用此 enum
的参数处理方法来实现。
这允许处理方法根据上下文控制方法的行为,包括通过状态来结构化枚举匹配。
使用示例
让我提醒你从 《rust-book》第 17.3 章“实现面向对象设计模式”中的条件。以下行为是必需的
pub fn main() {
let mut post = blog::Post::new();
post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());
post.request_review(); // without request_review() - approve() should not work
post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
使用宏 #[gen()]
这样解决
mod blog {
enum State {
Draft,
PendingReview,
Published,
}
pub struct Post {
state: State,
content: String,
}
#[methods_enum::gen(Meth, run_methods)]
impl Post {
pub fn add_text(&mut self, text: &str);
pub fn request_review(&mut self);
pub fn approve(&mut self);
pub fn content(&mut self) -> &str;
#[rustfmt::skip]
fn run_methods(&mut self, method: Meth) -> &str {
match self.state {
State::Draft => match method {
Meth::add_text(text) => { self.content.push_str(text); "" }
Meth::request_review() => { self.state = State::PendingReview; "" }
_ => "",
},
State::PendingReview => match method {
Meth::approve() => { self.state = State::Published; "" }
_ => "",
},
State::Published => match method {
Meth::content() => &self.content,
_ => "",
},
}
}
pub fn new() -> Post {
Post { state: State::Draft, content: String::new() }
}
}
}
在处理方法(在这种情况下,run_methods
)中,只需为每种状态编写哪些方法应该工作以及如何工作。
该宏在文档注释中重复编译器的输出。因此,在 IDE[^rust_analyzer] 中,你始终可以在枚举名称上方的弹出提示中看到生成的 enum
的声明和生成的代码块。
调用宏的语法
对于方法最多一个返回类型
#[methods_enum::gen()]
EnumName ,
handler_name]
其中
- EnumName: 自动生成的枚举名称。
- handler_name: 处理方法名称
对于方法超过一个返回类型
#[methods_enum::gen()]
EnumName ,
handler_name ,
OutName]
其中
- OutName: 从返回类型生成的自动枚举的变种名称。
链接
gen() 宏在限制和与方法和它们的输出值交互的易用性方面不如 impl_match!。gen() 的好处是它允许你看到完整的匹配表达式并处理更复杂的逻辑,包括非平凡输入表达式、匹配守卫和来自子状态枚举的嵌套匹配。
许可
您选择的 MIT 或 Apache-2.0 许可证。