Suddenly, I have noticed uncomfortable pattern in my code:
fn function() -> Result<T, E> {
// Do some stuff
Ok(
// Some chained call
variable
.do_stuff()
.do_try_stuff()?
.do_final_stuff()
)
}
So I was thought about T.into()
chained call for this case:
fn function() -> Result<T, E> {
// Do some stuff
// Some chained call
variable
.do_stuff()
.do_try_stuff()?
.do_final_stuff()
.into()
}
I think that this helps to make code more clear. And It must not break any existed code, because into Result
must be explicitly invoke by programmer (due to the orphan rule, If I understand all correctly):
Into::<Result<T, E>>::into(value)
I will glad to see any advices in such kind of situations and accept any critic. Have a nice day :D
Update 2023 / 02 / 05
After some discuss I decided to extend this post by several ideas. As for today we have these possible solutions...
Implement T -> Ok(T)
impl<T, E> From<T> for Result<T, E> {
fn from(t: T) -> Result<T, E> {
Result::Ok(t)
}
}
Pros:
- Doesn't breaks existed code
- Can be implemented right now (Example provided by @tspiteri: Rust Playground)
Cons:
- No option for
E -> Err(E)
Implement T: !Error -> Ok(T), E: Error -> Err(E)
impl<T: !Error, E> From<T> for Result<T, E> {
fn from(t: T) -> Result<T, E> {
Result::Ok(t)
}
}
impl<E: Error, T> From<E> for Result<T, E> {
fn from(e: E) -> Result<T, E> {
Result::Err(e)
}
}
Pros:
- Covered case when programmer wants to return
E.into()
Cons:
- Cannot be implemented right now due to negative traits problems: Github
rust-lang
issues - Probably will not implemented for several years?
Implement T -> Ok(T), E -> Err(E); Result<T, E>; T != E
I was thought and now I think that this is the best choice
impl<T, E> From<T> for Result<T, E>
where T: !E
{
fn from(t: T) -> Result<T, E> {
Result::Ok(t)
}
}
impl<T, E> From<E> for Result<T, E>
where T: !E
{
fn from(e: E) -> Result<T, E> {
Result::Err(e)
}
}
Pros:
-
Independed from any traits (depends only on
T != E
) -
Result<T, T>
must be covered explicitly (and that is good) -
Cover all possible cases (since
1
and2
- opposite cases) -
Probably have less problems that previous solution
I think so because we can represent
T != E
asT = !E
so we can deal with ONE type and ONE negative impl (if I'm not wrong)impl<T> From<T> for Result<T, !T> { fn from(t: T) -> Result<T, !T> { Result::Ok(t) } } impl<T> From<!T> for Result<T, !T> { fn from(e: !T) -> Result<T, !T> { Result::Err(e) } }
Cons:
- Requires some support from community to help rust core developers notice this idea
:D