So far I agree with all except
Some(2) || Some(3)
should produce Some(2)
Sebastian Malton
So far I agree with all except
Some(2) || Some(3)
should produce Some(2)
Sebastian Malton
Basically I want the following conversions via From
/ Into
:
true => Some(()) => Ok(())
false => None => Err(())
or the other way around:
Ok(T) => Some(T) => true
Err(E) => None => false
As long as these three works, and we have something we can extend to your own types, it matters less if we use Into<bool>
, Into<Option>
or Into<Result>
. Maybe even std::ops::Try
.
Good reply, this is exactly the kind of discussion I'd hoped to spark -- way more interesting that defining traits.
You have an associativity assumption in there. None || None || 2
could be None || (None || 2)
, at which point None || None
on its own never needs to work. (Though it still would, because None || None
can be None::<Option<T>> || None::<T>
.)
Then Some(1) || Some(2) || Some(3) || 4
compiles fine.
Edit: Hmm, Operator expressions - The Rust Reference says that ||
is left-to-right I wonder if that's actually observable anywhere today?
It would be if you are doing effectful actions behind a ||
, but that is code smell
false || { println!("Hello "); false } || { println!("World!"); true }
// or
check || return
Yes, all arithmetic operators have the same associativity precedence, so it would be bad to change ||
to a different one.
Both parenthesizations of that have the same output, though: Rust Playground
That said, I think this thread, on re-reading, has convinced me that Some(1) || Some(2)
ought to work and produce Some(1)
. (Unlike in the macro in my previous thread.)
Anyone have examples where the values from the two sides should be combined on some way? (Maybe @Nokel81 or @bugaevc, who posted traits with an or of some sort between the sides.)
Huh, well I guess I should check these things before I say things
If I understand what you're asking â I've written an impl that gives you Option<A> && Option<B> -> Option<(A, B)>
in the playground I've linked above, and one for Result<T, E> || Result<T, F> -> Result<T, (E, F)>
in response to @timvermeulen; but they're just examples of what's possible when the output type doesn't equal Self
and the answer includes a value from self
(not just depends on it) â if you ask me, I'd rather not have libcore provide those impls.
The difference between Option<((A, B), C)>
and Option<(A, (B, C))>
is also an example of when associativity matters.
That it does unless we can define a flattened tuple.
One useful impl we could also have is:
let opt: Option<T> = ...;
let res: Result<T, E> = opt || Err(e);
this is currently known as ok_or()
/ ok_or_else()
.
This would only work if the right hand side is an Err
variant; my first idea was to write the types as Option<T> || Result<!, E> -> Result<T, E>
; but the compiler rightfully points out that together with Option<T> || T -> T
this makes Option<Result<!, E>> || Result<!, E>
ambiguous. The workaround is to tie the type of the Ok
variant to T
somehow while keeping it uninhabited:
struct PhantomNever<T> {
phantom: PhantomData<T>,
never: !,
}
impl<T, E> LogicalOr<Result<PhantomNever<T>, E>> for Option<T> {
type Output = Result<T, E>;
...
}
Or, we could just allow generic Option<T> || Result<T, E> -> Result<T, E>
, but that would mean always throwing out the Ok()
variant of the RHS Result
.
Thoughts?
I think that this is fine
Should we allow the other way? I donât feel strongly either way
Sebastian Malton
How? Do you want Result<T> || Option<T>
? What would it return? Currently, we donât have any functions that do this right now, so there isnât any precedence for it.
Iâm guessing Result<T,E> || Option<T> = Option<T>
corresponding to res.ok().or_else(opt)
But how is this significantly better than just doing res.ok() || opt
, which is already quite terse. I donât see any reason to add this. Anyways, I donât think we need the other way, because it doesnât gain all that much.
I was thinking like that but I donât know if it makes enough sense to have explicitly instead of having people just call res.ok() || opt
Sebastian Malton
My biggest problem with this operator is that the error value on the left hand side is always ignored:
Ok(x) || Ok(y) = Ok(x)
Ok(x) || Err(y) = Ok(x)
Err(x) || Ok(y) = Ok(y)
Err(x) || Err(y) = Err(y)
We should thus extend the implementation for result to
Result<T,E1> || Result<T,E2> = Result<T, E2>
Even if we did this, we are implicitly ignoring a value, Maybe we would rather describe this relation as res1.ok() || res2
, so that it is explicit that we are ignoring a value
No the error on the right hand side would be ignored.
That seems even more arbitrary to me. My version coresponds to:
match $expr1 {
Ok(v) => Ok(v),
Err(_) => $expr2,
}
While what you are proposing would correspond to:
match $expr1 {
Ok(v) => Ok(v),
Err(e) => {
match $expr2 {
Ok(v) => Ok(v),
Err(e) => Err(e),
},
}
This requires holding on to the previous error, as well as performing an extra branching
I agree with
Result<T,E1> || Result<T,E2> = Result<T, E2>
It is simpler to implement, and it is semantically more clear. If we have an Ok
we return it, otherwise return the right result.
I think if I tried to spell out the behavior Iâd intuitively expect for Option
and Result
, Iâd end up giving the two types diametrically opposed behaviors that canât reasonably be merged.
Maybe this is just me, but my intuition is that for Result
, the Err()
case is an error, that its existence represents a non-happy path in control flow and something you do not want to âswallowâ by mistake. While with Option
, thereâs usually nothing erroneous or unhappy about None
. Similarly, when faced with a collection of Result
s, I typically want to either early return with the first Err
or proceed with all Ok
s, while a collection of Options
I typically want to either filter out all the None
s or just map
them over and over. This sort of thing leads my brain to tell me that Some(t) || None
should be Some(t)
, but Ok(t) || Err(e)
should be Err(e)
, and I donât think itâd make sense to do both of those, because then result.ok() || None
is legal code and my brain just goes ???
in that case.
Am I being super weird, or does anyone else feel a similar tension of intuitions?