Currently there is no way to specify return type of a async block other than having full annotated return value. In some cases it is not that easy either, e.g. if you have
I'm also wondering why an "immediately invoked closure" wouldn't be good enough. Unless I'm missing something, it'd be only a few extra characters over this proposal in all cases.
Ok::<(), MyError>(()) // <- note the explicit type annotation here
It's undesired because it's an unusual syntax and doesn't feel intuitive. And it's not quite doable in this case where there is an infinite loop.
This looks nice but I personally don't feel very comfortable with it.
Also I think it's really just imitating closures. There is no reason that any of the approaches you mentioned doesn't work with closures, but I still find it's clearer to use -> ReturnType for annotating return type of closure when it can't be induced otherwise.
It's probably just a personal feeling thing, though.
I’m not sure if the async_closure feature includes the syntax
async move || -> Result<(), Error> {
// ...
}()
Also note that I’m not necessarily against your idea (just against the use of an arrow). I would (personally) probably prefer it to look like this, especially also to more visibly differentiate it from the closure variant
async move Result<(), Error> {
// ...
}
One should also consider that something like this could be useful with try blocks. In case we really want this, we should consider how this works syntactically similar for both.
I initially thought that using an async closure would be more limiting than an async block, because you can return, break, continue and ? across regular blocks but not across closures. However, control flow in async blocks has the same limitations as in closures. So if I'm not mistaken, it's always possible to convert an async block to an immediately-invoked async closure.
That would only help when you assign the value to a local variable. async-block, however, just like closures, very often used as function arguments, and the function parameters are usually generic themselves as well. For example,
It's actually more tricky when it comes to try blocks. If we follow the idea of async function, the annotation should be the Future's Output type rather than the Future itself. And since try block is probably going to get Ok-wrapping, its type annotation would probably need to be the type of the Ok branch... which could be more controversial.
One option would be to lean into the fact that an async block is a closure to disconnect it from try blocks and tie the syntax more towards Fn-closures as proposed in the OP (there's a reason async move is a thing but try move isn't). Then, similar to Fn-closures being annotated with the type the closure returns, not the type of the closure, Future-closures should be annotated with the type the closure returns, not the type of the closure. (And async closures which are Fn-closures returning Future-closures are annotated with the final return type of both closures, not the type of either of the closures making it up).
There are lots of ways to make it possible for sure.
Rust doesn't go the Python path that everything should have only one right way, so there are lots of things which can be done otherwise still have some syntax sugar to make it either more straightforward or more ergonomic.
All your tricks can apply to closures as well, but as I mentioned before, I still believe the return type annotation of closure is nicer in general.
No it doesn't. However that shouldn't mean that just every old redundant feature can or should be thrown in, because such features tend to cruft up any language that incorporates them on the long run.
Maybe you remember that inheritance in OO languages was once heralded as a really nifty feature. But that hype didn't mean the feature was pulling its weight, and by the time folks figured that out, it was way too late for the languages that incorporated them.
Add a few more of such misfeatures (e.g. enums that aren't ADTs but semi-useless sugar for classes and thus can't be matched on, or a type system that isn't quite comprehensive enough, etc) and you get Java, essentially.
If you want syntax sugar for specifying types, you can always reach for a macro. It'll work without having to burden the language with features that are not only a bad idea (my opinion) but redundant (fact).