Enum Usage - Alternatives?

I'm trying to make something that's human-readable where I can use names instead the actual code of a number. I thought enums would be used for this but after trying to check if a number is the value of an enum, I'm having second thoughts. Here's my implementation, feel free to critique what I'm doing.

There are 3 codes for this endpoint. The have 3 different functions, I just wanted the code to be more readable without using the codes themselves.

// Card Command Codes
#[derive(Debug, Deserialize)]
pub enum CardCmd {
    READ = 256,
    REGISTER = 512,
    REISSUE = 1536,
}

#[derive(Debug, Deserialize)]
pub struct CardVals {
    cmd_str: CardCmd, // Commands for card functions
    card_no: u64, // Example: 7020392002385103
}

#[post("/service/card/cardn.cgi")]
async fn cardn(web::Form(form): web::Form<CardVals>) -> HttpResponse {
    dbg!(&form);
    match form.cmd_str {
        CardCmd::READ as u16 => {
            println!("READ");
            resp!("")
        },
        CardCmd::REGISTER as u16 => {
            println!("REGISTER");
            resp!("")
        },
        CardCmd::REISSUE as u16 => {
            println!("REISSUE");
            resp!("")
        }
    }
}

This code doesn't compile btw but I'd like something similarly done!

Here's your example fixed in the playground.

2 Likes

Sorry, I didn't specify, the arg.cmd_str is a number of the enum (in this case its 256).

This is a requirement with the client I'm working with (context -> this is a server emulator for an arcade machine).

Here is what the requirement looks like with the playground code you provided:

pub enum CardCmd {
    Read = 256,
    Register = 512,
    Reissue = 1536,
}

pub struct CardVals {
    pub cmd_str: CardCmd, // Commands for card functions
    pub card_no: u64, // Example: 7020392002385103
}

fn main() {
    cardn(CardVals {
        cmd_str: 256,
        card_no: 1u64
    });
    cardn(CardVals {
        cmd_str: 512,
        card_no: 2u64
    });
    cardn(CardVals {
        cmd_str: 1536,
        card_no: 3u64
    });
}

fn cardn(arg: CardVals) -> () {
    match arg.cmd_str {
        CardCmd::Read => {
            println!("READ: {}", arg.cmd_str as usize);
        },
        CardCmd::Register => {
            println!("REGISTER: {}", arg.cmd_str as usize);
        },
        CardCmd::Reissue => {
            println!("REISSUE: {}", arg.cmd_str as usize);
        }
    }
}

You are confusing how deserialization works. Run the code in the playground and see how it works.

1 Like

Use strum or similar to derive

2 Likes

Ok after looking around I came to this conclusion. I wanted to have something clean and not reliant on another crate, but strum does look interesting.

// Card Command Codes
#[derive(Debug, Deserialize)]
pub enum CardCmd {
    READ = 256,
    REGISTER = 512,
    REISSUE = 1536,
}

impl CardCmd {
    fn from_u16(cmd_str: u16) -> Option<Self> {
        match cmd_str {
            256 => Some(CardCmd::READ),
            512 => Some(CardCmd::REGISTER),
            1536 => Some(CardCmd::REISSUE),
            _ => None, // Handle unknown values
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct CardVals {
    cmd_str: u16, // Commands for card functions
    card_no: u64, // Example: 7020392002385103
}

#[post("/service/card/cardn.cgi")]
async fn cardn(web::Form(form): web::Form<CardVals>) -> HttpResponse {
    dbg!(&form);
    match CardCmd::from_u16(form.cmd_str) {
        Some(CardCmd::READ) => {
            println!("READ");
            resp!("")
        },
        Some(CardCmd::REGISTER) => {
            println!("REGISTER");
            resp!("")
        },
        Some(CardCmd::REISSUE) => {
            println!("REISSUE");
            resp!("")
        },
        None | Some(..) => todo!(),
    }
}

If any of you have any suggestions on my choice, please let me know. I don't like how it adds more code but it now works the way I wanted it to work.

Thanks all!

What's not "clean" about using a crate for exactly what it was designed for? It's far worse to re-implement the same functionality, especially if it's pure boilerplate that could be abstracted away with a macro.

2 Likes

After some thought, I see why this is true.

1 Like

Another benefit of derive macros like those provided by strum is that it's impossible to forget to update the from_u16 equivalent when you add a new variant.

Incidentally, if you might add a new variant, add #[non_exhaustive] to your enum. Then consumers outside of your crate will have to deal with the possibility of an unknown variant (making them more forward compatible / making it possible for you to add a new variant without it being a semver breaking change).

3 Likes

I'm struggling with implementation, can you show an example on how I can do this for a web server point of view?
All I've gotten it to do so far is return a string instead of the actually associated value with the Enum.

There's nothing intrinsically web-server or non-web-server about being able to go between a typed enum representation and between other representations like a u16 or &str.

Here's a playground with some strum examples. strum isn't available on the playground, so if you want to run it you'll have to install strum in your project:

cargo add strum --features=derive

Example output:

Read: 256
Reissue: 1536
Register: 512
[src/main.rs:63] CardCmd::try_from(0) = Err(
    TryFromU16Error(
        0,
    ),
)
[src/main.rs:64] CardCmd::try_from(256) = Ok(
    Read,
)
[src/main.rs:65] "Read".parse::<CardCmd>() = Ok(
    Read,
)
[src/main.rs:66] "Garbage".parse::<CardCmd>() = Err(
    VariantNotFound,
)
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.