This was difficult to title, so please bare with me while I try to explain
I am converting from one struct BigThing
with lots of optional fields to a smaller, more specialised struct SmallThing
with fewer fields that aren't optional.
I ended up with the following code, but I'm curious whether there are alternative ways to write this that avoid infallible unwraps - or at least, what I think are infallible unwraps!
impl TryFrom<BigThing> for SmallThing {
type Error = Report<ConvertError<SmallThing>>;
fn try_from(big: BigThing) -> Result<Self, Self::Error> {
// check if it's none, avoiding moving the sub field
if big.data.code.is_none() {
return Err(ConvertError::new())
.into_report()
// this moves the BigThing, which causes my headache
.attach_lazy(|| big)?;
}
if big.data.text.is_none() {
return Err(ConvertError::new())
.into_report()
// as does this
.attach_lazy(|| big)?;
}
let code = big.data.code.unwrap();
let text = big.data.text.unwrap();
Ok(SmallThing {
metadata: big.metadata,
code,
text,
})
}
}
Trying the "obvious?" thing, I get errors due to a partially moved value, and I can see what the compiler is saying.
let code = big.data.code
.ok_or_else(ConvertError::new)
.into_report()
.attach_lazy(|| big)?;
// ^^^^^^ error[E0382]: use of partially moved value
ok_or_else moves self, so I lose the big.data.code
field, and therefore can't use big
at all later.
pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
I could clone
big, but since I only want to move when there's an error I'd rather avoid the cost to do so.
Should I be satisfied with my unwrap strategy?
Can you think of a nicer solution?
Oh, and if you're curious, Report
, into_report()
and attach_lazy
come from error_stack
which I've been enjoying using so far! It can be verbose but it feels like it might be the right error handling solution for me.
It's not supported in the playground but I rigged something up to demonstrate the problem: Rust Playground