Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grouping structs with enums

Tags:

rust

In Rust, how should one go about grouping related structs so that a function signature can accept multiple different types while refering to the concrete type inside the method body?

The following example is contrived for simplicity:

enum Command {
    Increment {quantity: u32},
    Decrement {quantity: u32},
}

fn process_command(command: Command) {
    match command {
        Command::Increment => increase(command),
        Command::Decrement => decrease(command),
    };
}

fn increase(increment: Command::Increment) {
    println!("Increasing by: {}.", increment.quantity);
}

fn decrease(decrement: Command::Decrement) {
    println!("Decreasing by: {}.", decrement.quantity);
}

fn main() {
    let input = "Add";
    let quantity = 4;

    let command: Command = match input {
        "Add" => Command::Increment { quantity: quantity },
        "Subtract" => Command::Decrement { quantity: quantity },
        _ => unreachable!(),
    };

    process_command(command);
}

Compiling results in the following two errors:

src/main.rs:13:24: 13:42 error: found value name used as a type: DefVariant(DefId { krate: 0, node: 4 }, DefId { krate: 0, node: 5 }, true) [E0248]
src/main.rs:13 fn increase(increment: Command::Increment) {
                                      ^~~~~~~~~~~~~~~~~~
src/main.rs:17:24: 17:42 error: found value name used as a type: DefVariant(DefId { krate: 0, node: 4 }, DefId { krate: 0, node: 8 }, true) [E0248]
src/main.rs:17 fn decrease(decrement: Command::Decrement) {
                                      ^~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors

If I declare the structs seperately, and wrap the structs within a tuple struct (correct terminology?) each within the enum then I get the expected result, but with the verbosity and similar type names all over the place I suspect that I've misunderstood someting:

struct Increment {
    quantity: u32,
}

struct Decrement {
    quantity: u32,
}

enum Command {
    Increment(Increment),
    Decrement(Decrement),
}

fn process_command(command: Command) {
    match command {
        Command::Increment(increment) => increase(increment),
        Command::Decrement(decrement) => decrease(decrement),
    };
}

fn increase(increment: Increment) {
    println!("Increasing by: {}.", increment.quantity);
}

fn decrease(decrement: Decrement) {
    println!("Decreasing by: {}.", decrement.quantity);
}

fn main() {
    let input = "Add";
    let quantity = 4;

    let command: Command = match input {
        "Add" => Command::Increment(Increment { quantity: quantity }),
        "Subtract" => Command::Decrement(Decrement { quantity: quantity }),
        _ => unreachable!(),
    };

    process_command(command);
}

Running outputs:

Increasing by: 4.

Is wrapping the struct within an enum type (terminology?) sharing the same name really the best solution? Command::Increment(Increment { quantity: 7 })

like image 641
Peter Horne Avatar asked Mar 16 '15 23:03

Peter Horne


2 Answers

Yes, this is the best you will get along this line of implementation. An enum is one type only; its variants are purely that—variants, not types.

Another alternative is to use a trait and generics:

struct Increment {
    quantity: u32,
}

struct Decrement {
    quantity: u32,
}

trait Command {
    fn process(self);
}

impl Command for Increment {
    fn process(self) {
        println!("Increasing by {}", self.quantity);
    }
}

impl Command for Decrement {
    fn process(self) {
        println!("Decreasing by {}", self.quantity);
    }
}

Of course, it’s not a direct parallel; if you want to store a command of potentially differing types you’ll need to change process to take self: Box<Self> or &self, and you’ll need to work with either Box<Command> or &Command, but it’s another way of doing things that may suit your requirements. And as far as the definitions are concerned, it’s purer.

like image 158
Chris Morgan Avatar answered Oct 16 '22 16:10

Chris Morgan


I may be misunderstanding your simple example, but remember that you can implement methods on enums directly:

enum Command {
    Increment {quantity: u32},
    Decrement {quantity: u32},
}

impl Command {
    fn process(self) {
        match self {
            Command::Increment { quantity } => {
                println!("Increasing by: {}.", quantity)
            },
            Command::Decrement { quantity } => {
                println!("Decreasing by: {}.", quantity)
            },
        };
    }
}

fn main() {
    let input = "Add";
    let quantity = 4;

    let command: Command = match input {
        "Add" => Command::Increment { quantity: quantity },
        "Subtract" => Command::Decrement { quantity: quantity },
        _ => unreachable!(),
    };

    command.process();
}

I happen to like this version because it eliminates the redundancy of process_command(command).

like image 20
Shepmaster Avatar answered Oct 16 '22 16:10

Shepmaster