4个版本
0.0.4 | 2022年11月23日 |
---|---|
0.0.3 | 2022年11月22日 |
0.0.2 | 2022年11月21日 |
0.0.1 | 2022年11月18日 |
#2049 in 过程宏
20KB
299 行
实验性过程宏,从模板枚举生成部分填充枚举,以及这些枚举之间的有效形态。目标是定义一次包含所有可能变体的枚举,并生成部分枚举以约束不同的API到不同的变体子集,无需为每个API重新定义新的枚举。生成的形态可以用来在不同枚举之间进行转换,并轻松组合API。
lib.rs
:
一个用于从模板枚举生成部分枚举的过程宏。这个部分枚举具有与模板枚举相同的变体数量,但在编译时可以禁用其中的一组变体。目标是针对每个API使用具有更细粒度变体集的枚举进行特殊化。
这对于处理错误很有用。一个常见的模式是定义一个包含所有可能错误的枚举,并使用它来覆盖整个API表面。尽管简单,但这种表示可能无法精确表示错误场景,因为它允许发生不可能的错误。
考虑一个负责从套接字解码消息的API。
enum Error {
Connect(ConnectError),
Read(ReadError),
Decode(DecodeError),
}
fn connect() -> Result<Socket, Error> {
Ok(Socket)
}
fn read(sock: &mut Socket) -> Result<Bytes, Error> {
Ok(Bytes)
}
fn decode(bytes: Bytes) -> Result<Message, Error> {
Err(Error::Decode(DecodeError))
}
相同的错误枚举被到处使用,并暴露了不匹配API的变体:decode
返回一个 DecodeError
,但没有阻止返回一个 ConnectError
。对于这样的底层API,我们可以用与 connect
匹配的错误(如 ConnectError
)替换 Error
。缺点是,与这些函数组合需要我们重新定义自定义枚举
enum NextMessageError {
Read(ReadError),
Decode(DecodeError),
}
impl From<ReadError> for NextMessageError {
fn from(err: ReadError) -> Self {
NextMessageError::Read(err)
}
}
impl From<DecodeError> for NextMessageError {
fn from(err: DecodeError) -> Self {
NextMessageError::Decode(err)
}
}
fn read(sock: &mut Socket) -> Result<Bytes, ReadError> {
Ok(Bytes)
}
fn decode(bytes: Bytes) -> Result<Message, DecodeError> {
Err(DecodeError)
}
fn next_message(sock: &mut Socket) -> Result<Message, NextMessageError> {
let payload = read(sock)?;
let message = decode(payload)?;
Ok(message)
}
这个过程宏旨在通过生成一个新的泛型枚举来简化API的组合,其中每个变体可以逐个禁用。然后我们可以像这样重新定义我们的API
#[derive(partial_enum::Enum)]
enum Error {
Connect(ConnectError),
Read(ReadError),
Decode(DecodeError),
}
use partial::Error as E;
fn connect() -> Result<Socket, E<ConnectError, !, !>> {
Ok(Socket)
}
fn read(sock: &mut Socket) -> Result<Bytes, E<!, ReadError, !>> {
Ok(Bytes)
}
fn decode(bytes: Bytes) -> Result<Message, E<!, !, DecodeError>> {
Err(DecodeError)?
}
fn next_message(sock: &mut Socket) -> Result<Message, E<!, ReadError, DecodeError>> {
let payload = read(sock)?;
let message = decode(payload)?;
Ok(message)
}
请注意,next_message
的实现未更改,签名清楚地表明只有 ReadError
和 DecodeError
可以返回。调用者永远不会匹配到 Error::Connect
。decode
的实现使用 ?
操作符将 DecodeError
转换为部分枚举。通过使用夜间功能 exhaustive_patterns
,匹配语句甚至不需要编写禁用变体。
#![feature(exhaustive_patterns)]
fn read_one_message() -> Result<Message, Error> {
let mut socket = connect()?;
match next_message(&mut socket) {
Ok(msg) => Ok(msg),
Err(E::Read(_)) => {
// Retry...
next_message(&mut socket).map_err(Error::from)
}
Err(E::Decode(err)) => Err(Error::Decode(err)),
}
}
Rust 版本
默认情况下,空占位符是单元类型 ()
。生成的代码与稳定编译器兼容。当启用 never
功能时,使用 never 类型 !
代替。这需要夜间编译器和夜间功能 #![feature(never_type)]
。
依赖项
~1.5MB
~35K SLoC