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 Results, I typically want to either early return with the first Err or proceed with all Oks, while a collection of Options I typically want to either filter out all the Nones 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?