#enums #variant #partial #error #generate #partially #proc-macro

nightly macro partial-enum

生成部分填充枚举的过程宏

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 过程宏

MIT 许可证

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 的实现未更改,签名清楚地表明只有 ReadErrorDecodeError 可以返回。调用者永远不会匹配到 Error::Connectdecode 的实现使用 ? 操作符将 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