Hi everyone! I've been working on a shell scripting framework that utilizes the incoming Swift concurrency features. I'm really happy with how it is coming along, and am excited to use it to replace bash scripts in some of my other projects, as well as avoid using python for this kind of thing. While it needs a few more (already planned) features in the Swift compiler to become truly useful, working on it was an interesting case study in using the new concurrency APIs. If you are interested in taking a look at the code, you can find it here:
In the meantime, I've kept some notes about my experiences with the new concurrency APIs (and some more general observations) which might be interesting to the community. You should read this as one developer's experience using (possibly misusing) the new concurrency features in a single project rather than commentary on the current or planned state of these features in general.
The proliferation of
I did write
await a ton to make this work. Initially it really tripped me up that
async throws is in the opposite order as
try await, but towards the end my brain figured it out!
Much of the the excess
try awaiting was due to autoclosures not working (yet), but there are a few other cases where we could probably do better.
Most notably, it would be great to be able to leave these indicators off of single-expression closures which are being passed to expressions already being awaited on. We don't currently do this for
try and I haven't seen this discussed before, but there is an analogy to be made with the already-optional
return in these cases.
Also, I'm assuming this is already on folks' radar, but I had to write
await twice when I iterated over an
AsyncSequence returned from an
An exception for top-level code has also been discussed, though I see this code mostly residing in the
main function of a swift-argument-parser
ParseableCommand so I'm not sure how much that will help.
I ran into this error in my first attempt at this code (
shell.subshell sets a task local variable):
Here, I only care about the result of the second
group.spawn and initially I tried to just put lines 32-34 in the
withThrowingTaskGroup body so I could get at the result directly. I'm sure there is a good reason for this (and the error message was so long it was cut off in Xcode :-) but I still haven't quite internalized the reasoning.
I ended up having to write things like this:
I expect most packages to just implement stuff like this so it probably won't be too painful.
This doesn't have anything to do with concurrency, but I used single-case-enums to enable two different semantics for swift interpolation (
Name here are enums with a single case each;
name, respectively). In this case, using
.name enables extra parameters to be provided to the interpolation. It works well, but feels a little hacky, and I'm not sure what a better approach would be: