The call to ExecutorJob.runSynchronously(on:) must happen-after the call to enqueue(_:).
However, the type of the parameter of enqueue is consuming ExecutorJob, which is non-copyable.
This seems contradictory to the above requirement that this parameter must escape enqueue, which (in my understanding) is the only way it could be scheduled (and thus consumed) after the invocation period of enqueue.
So what's the point of defining this ExecutorJob type in the library, should I stick to using the old UnownedJob type?
Maybe I am misunderstanding, but why would the consuming keyword in the enqueue function prevent the parameter to "escape" it? What it does is that it indicates to the caller that they lose ownership of the ExecutorJob, it does not prevent the enqueue function from storing it elsewhere. So I think all the proposal says there is that you have to first enqueue each ExecutorJob before, later, you call runSynchronously on it (or an UnownedExecutorJob you created from it).
That's because runSynchronously is a consuming function, and when a noncoypable is captured in a closure, you cannot perform consume operations on it.
Of course I can write some wrapper, or transform it into UnownedExecutorJob, but that's just the question I asked. Why does ExecutorJob exist in the first place?
Now I see what you mean, basically code like in the proposal itself. Yes, there they indeed immediately transform it into an UnownedExecutorJob like this (to pass it to an asynchronous function).
My guess here is that the ExecutorJob (which is not "unowned") is meant to be useful when you store it in some form of buffer instead of enqueueing it into e.g. a GCD queue immediately.
Depending on what underlying mechanism the thing that actually executes the job has, you might not have a queue that you can immediately call async { ... } on. It might periodically check said buffer (which would probably require careful synchronization), pick a job and then call runSynchronously(on:) on it. Using an UnownedExecutorJob on this would probably be difficult, as you have to manage the lifetime for it yourself (I am guessing here, but I assume that as the name implies that it does not participate in the normal memory management stuff like retains and so on). ExecutorJob looks like a normal (if ~Copyable) struct to me which you can treat like any other type without having to care about when or how it is destroyed.
Keep in mind an executor could be something entirely different from a GCD queue. A silly idea in my mind would be a dedicated thread that inverts task priority or introduces artificial delays in between running jobs (for demonstration purposes).
If such a thing has its own runloop-like polling and cannot be triggered directly from within enqueue, then all the function can do is put it in some sort of shared storage. And as said I guess that UnownedExecutorJob is unfit for that.
Disclaimer: I am definitely not deep enough into these levels of concurrency, all this is meant to be is a fools theory.