Description
I started a mailing list thread on this topic about a week ago; in the first post I summarised the situation. I'll quote it here verbatim:
(Do I win the prize for the shortest thread name yet?)
error: last argument in
do
call has non-procedure type: ||In the past three or four days I've seen at least as many enquiries in #rust about this error, and I'm sure there have been at least several others while I haven't been monitoring it. This is evidently causing quite a bit of confusion.
Here's a summary of the change. The syntax is the same as it was before:
do expr { block } do expr |args| { block } do expr(args) { block } do expr(args) |args| { block }
These used to desugar to the following, respectively:
expr(|| { block }) expr(|args| { block }) expr(args, || { block }) expr(args, |args| { block })
These now desugar to the following, respectively:
expr(proc() { block }) expr(proc(args) { block }) expr(args, proc() { block }) expr(args, proc(args) { block })
The change is that it now accepts a procedure rather than a closure. No syntax change, just a semantics change which breaks a lot of code.
Closure: a stack function; used to be
&fn(..) -> _
, is now|..| -> _
. Can be called multiple times, requires no allocations and is not Send.Procedure: a heap function; used to be
~once fn(..) -> _
, is nowproc(..) -> _
. Can be called once, requires heap allocation and is Send.Procedures are good for sending cross-task; things like the task body are a good match. Still, I think there are a few problems with how things are at present (i.e. after the do semantics change):
do
is still using the syntax of a closure (|..| { .. }
), despite it now being a procedure.- All of a sudden, things using closures need to shift away from using
do
or use procedures; this is causing confusion and may cause bad design decisions where nice sugar triumphs over what is actually needed; often the best solution may not be clear. (I, for example, had not thought about the fact thatproc
was going to allocate; the~once fn
name was clearer about that. I'll speak about&once fn
another time. Don't mention it now, this thread is just aboutdo
.)I have two solutions that I think could answer these concerns. Leaving it as it is seems a bad idea to me.
(a) Kill
do
I've had mixed feelings about
do
. Overall, it's pretty trivial syntax sugar, but it's sugar of a dubious sort, because it changes something that looks like a function call with N arguments to be a function call with N+1 arguments. That's just a matter of learning it.Still,
do
is nice sugar in the way it gets rid of parentheses at the end. Overall, is it worth it? I don't know.Once
do
is gone, there's no problem left: just remove the sugar everywhere it was used and everything works and will do for the foreseeable future.(b) Make
do
support both closures and proceduresThe syntax of
do
can be clearly seen to include the closure syntax. We could easily extend it to support both closures and procedures.Here is a proposed
do
using closures once more, keeping the syntax it had last week:do expr || { block } do expr(args) || { block } do expr |args| { block } do expr(args) |args| { block }
Here is a proposed
do
using procedures as the current behaviour is, but with new syntax which is clearly a procedure:do expr proc() { block } do expr(args) proc() { block } do expr proc(args) { block } do expr(args) proc(args) { block }
This does leave these cases which are currently valid unclear:
do expr { block } do expr(args) { block }
The options for this are (a) disallowing it; (b) making it always of the function types; and (c) inferring the type. I generally prefer the last solution but it is the most difficult. I'm not sure how it all fits into the function traits stuff at all.
Incidentally, all this leaves the possibility open of making
do
work for any argument type, wheredo expr1 expr2
simply desugars toexpr1(expr2)
anddo expr1(args) expr2
todo expr1(args, expr2)
. I don't know if that would be a good thing or not; it's probably best to avoid discussion of that at present.Summary
Leaving
do
in its present form seems to me a distinctly bad idea, with the syntax of one form of function while it uses another form of function. I think we need to redodo
very soon. (I'd save this joke for later in the thread, but I'm afraid someone else might steal it. I expect all responses to indicate they're in favour of this by using the title "Re: do" :P.)For myself, I have no preference to indicate; I am torn between the two options.
After some discussion, I think that for the moment, removing do
is probably the better option. We can then see about adding it or something similar back later if we want to.
So then, concrete proposal: remove do
from the language.