Since one can think of ? as and_then, would it make sense for there to be some kind of standard macro or operator for or_else? Like how C# has the null coalescing operator?? (which spawned the or_else conversation) and SQL has the COALESCE function.
As I’ve been spending a bunch of time with ops::Try lately, I realized we could do something like this:
I definitely think that this operation is useful for Rust (I mean, we have it in method form for both main Try types), and that generalizing it like this is cool. The continuing comma support for the standalone coalesce! is nice but probably not desired for a postfix macro or operator.
I think the only operator that would make sense is ||. It’s already lazy, as this one would be, and currently isn’t overloadable so it wouldn’t break anything. (And you can imagine a impl Try for bool where b? short-circuits on false which would be perfectly consistent with this definition, though I suspect we don’t actually want to allow ? on bool.)
OTOH, maybe just the macro would be enough. A bunch of the try{} discussions have talked about “what if I just want to return the first success?”, where it might be good not to have that use try/?, but instead use something like
Only problem with this is that it may be blocked on precise closure captures because we are hiding a closure in there. Otherwise I think this is a great idea!’
edit: as @scottmcm , this won’t work due to backwords compatibility issues.
It is uncommon for conditionnally executed code to take ownership of something ;
And when it happens, you can always type hint using a shadowing explicitely typed let binding within the closure
Example:
fn main ()
{
let s = String::from("foo");
::std::thread::spawn(|| {
let s: String = s;
println!("{:?}", s);
});
}
When taking ownership of something is required for a closure, the compiler is suprisingly good at figuring that one out. The one exception is ::std::thread::spawn since the other thread may just mutate or read the captured environment, and the reason move is needed is because of the 'static lifetime bound on the closure (not its FnOnce-ness). It doesn't look like the kind of problem involved in this or_else sugar scenario
In fact, my original design should work even for early returns. It just require the compiler to generate a special entry for return or break or continue, and let the generated function body jump directally to it, leaving an expression typed !.
i.e.
a || b desugar to a.or(b)
a || return c desugar to a.or(|| _compiler_return_glue(c))
As _compiler_return_glue(c) is !, the above should type check.
Yes, that is true, I didn't state my problem clearly.
false || {
/* your code here */
};
where the block evaluates to !, and uses return, continue, or break with non-trivial control flow. There wouldn't be a way to desugar this. Therefore this change would require a breaking change.
false.or(|| loop{}) is also always goes into an infinite loop, if or is the usual definition. But true || loop {} or true.or(|| loop{}) will not loop at all.
I already proposed my solution.
a || return b => a.or(|| __return_label(b))
a || break b => a.or(|| __break_label(b))
a || continue => a.or(|| __continue_label())
Only the compiler can generate those labels, and those instructions are just jmp to the label.