I’m very new to Rust (coming from Python) and am working through past Advent of Codes as an avenue to learn the language more fully.
As part of one of the problems, I created a function to validate inputs read from a file, returning an enum to tell how to proceed:
enum ValidatorReturns {
BRK,
CNT,
VALUES(i32, i32)
}
fn validate_inputs(contents: &mut String) -> ValidatorReturns {...}
fn pt2() -> Result<(), std::io::Error> {
let file = File::open("input.input")?;
let mut reader = BufReader::new(file);
...
loop {
let mut contents = String::new();
let _ = reader.read_line(&mut contents)?;
let mut val1: i32 = 0; // <-- warning on never-read
let mut val2: i32 = 0; // <-- ditto
let _ = match validate_inputs(&mut contents) {
ValidatorReturns::BRK => break,
ValidatorReturns::CNT => continue,
ValidatorReturns::VALUES(tval1, tval2) => {
val1 = tval1; // Ugly use of temp var names
val2 = tval2;
}
};
...
}
...
}
This works, but as I’ve commented, it leads to never-read warnings, has fairly ugly use of temp var names, and feels overall messy.
- Coming from Python, I was inclined to try returning
val1, val2as a tuple from the match statement, then unpack (rather thanlet _ = match ..., however that didn’t work and I was unable to find similar examples anywhere. - I also tried to declare both variables in the match-response block (i.e.,
let val1 = tval1;), and that led to an undeclared-variable compile error (which I can understand why, as it’s in a subscope). - I considered also just returning multiple bools (the first representing
BRK, the secondCNT, and then the two validated values) however this would be messy and seems un-Rust-like.
After some reading of the docs, I refactored it to use a struct as the enum member rather than storing the values directly in the enum:
struct ValidatedValues {
val1: i32,
val2: i32
}
enum ValidatorReturns {
BRK,
CNT,
VALUES(ValidatedValues)
}
fn validate_inputs(contents: &mut String) -> ValidatorReturns {...}
fn pt2_enum() -> Result<(), std::io::Error> {
let file = File::open("input.input")?;
let mut reader = BufReader::new(file);
...
loop {
let mut contents = String::new();
let _ = reader.read_line(&mut contents)?;
let valvalues = match validate_inputs(&mut contents) {
ValidatorReturns::BRK => break,
ValidatorReturns::CNT => continue,
ValidatorReturns::VALUES(val) => {val} // <-- Still feels messy
};
...
}
...
}
It seems like this would be a common pattern with elegant syntax, but having to specify that I want the enum seems rather messy and unconsidered, in contrast to most other Rust syntax; that leads me to wonder if there’s a better way to handle this. This answer to another question has the phrase "An enum is one type only; its variants are purely that—variants, not types." That makes sense to me, though the solution I’ve outlined above still strikes me as a somewhat messy solution for what should be a common scenario – especially if it needs to be done per function. For example, the Option enum has ? or .unwrap() to extract the "main" value.
Is using a struct the best/correct way to handle returning multiple values which must be part of an enum? Or, alternatively, is there a better approach altogether which I’m missing?
>Solution :
Use a tuple. Here’s how:
let (val1, val2) = match validate_inputs(&mut contents) {
ValidatorReturns::BRK => break,
ValidatorReturns::CNT => continue,
ValidatorReturns::VALUES(tval1, tval2) => {
(tval1, tval2)
}
};