I'm trying to parse some user input, the input is stored in a struct where there are a lot of options/results (to deal with the data being uninitialized before the user enters it) and when the user submits it it needs to go into a struct where all the values are known.
I'd like to be able to a vector with any errors so I can give feedback to the user on all the errors with the input (not just the first). However, I can't find a good way to do that.
struct Test {
x: i32,
y: String,
}
#[derive(Debug)]
enum Errors {
OneIsNone,
TwoIsNone,
}
The my current approach is:
fn main() -> Result<Test, Vec<Errors>> {
let option1 = Some(5);
let option2 = None;
let mut errors = Vec::new();
let x: i32;
match option1 {
Some(value) => x = value,
None => errors.push(Errors::OneIsNone),
};
let y: String;
match option2 {
Some(value) => y = value,
None => errors.push(Errors::TwoIsNone),
}
if errors.len() != 0 {
return Err(errors)
}
Ok(
Test {
x,
y
}
)
}
Which doesn't work because the compiler can't tell that x and y must be initialised if the code reaches the Ok
block.
In this case I could just unwrap match the two results but that approach will become hideously clunky for structs with a large number of fields.
Basically I want a way to take in a large number of results and parse them into a struct if they all give Ok()
or return a vector containing all the errors otherwise i.e. how to I implement unwrap_or_errors
in
fn main() -> Result<Test, Vec<Errors>> {
let option1 = Some(5);
let option2 = None;
let mut errors = Vec::new();
let x = match option1 {
Some(val) => Ok(val),
None => Err(Errors::OneIsNone),
};
let y = match option2 {
Some(val) => Ok(val),
None => Err(Errors::TwoIsNone),
};
if errors.len() != 0 {
return Err(errors)
}
Test::unwrap_or_errors(x, y)?
}
You can use tuple pattern matching for the success case, and iterator flattening for the failure case:
struct Test {
x: i32,
y: String,
}
impl Test {
fn unwrap_or_errors<E>(
x: Result<i32, E>,
y: Result<String, E>,
) -> Result<Self, Vec<E>> {
match (x, y) {
(Ok(x), Ok(y)) => Ok(Self { x, y }),
(x, y) => Err([x.err(), y.err()]
.into_iter()
.flatten()
.collect()),
}
}
}
#[derive(Debug)]
enum Errors {
OneIsNone,
TwoIsNone,
}
fn new() -> Result<Test, Vec<Errors>> {
let option1 = Some(5);
let option2 = None;
let x = match option1 {
Some(val) => Ok(val),
None => Err(Errors::OneIsNone),
};
let y = match option2 {
Some(val) => Ok(val),
None => Err(Errors::TwoIsNone),
};
Test::unwrap_or_errors(x, y)
}
This is still slightly redundant, but you can switch to a macro to fix that:
struct Test {
x: i32,
y: String,
}
macro_rules! unwrap_or_errors {
($($id: ident),*) => {
match ($($id),*) {
($(Ok($id)),*) => Ok(Test { $($id),* }),
($($id),*) => Err([$($id.err()),*]
.into_iter()
.flatten()
.collect()),
}
}
}
#[derive(Debug)]
enum Errors {
OneIsNone,
TwoIsNone,
}
fn new() -> Result<Test, Vec<Errors>> {
let option1 = Some(5);
let option2 = None;
let x = match option1 {
Some(val) => Ok(val),
None => Err(Errors::OneIsNone),
};
let y = match option2 {
Some(val) => Ok(val),
None => Err(Errors::TwoIsNone),
};
unwrap_or_errors!(x, y)
}