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.
In No Particular Order...
The proliferation of try
and await
I did write try
and 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 await
ing 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 async
getter.
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.
Binding a task-local value is illegal within the body of a withTaskGroup invocation
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.
Not being able to throw from defer resulted in some extra boilerplate
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.
String Interpolation Hack
This doesn't have anything to do with concurrency, but I used single-case-enums to enable two different semantics for swift interpolation (Value
and Name
here are enums with a single case each; value
and 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: