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 })
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.
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)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With