Swift 6.2: Subprocess
Swift 6.2 introduces a new
Subprocesspackage that offers a streamlined, concurrency‑friendly API for launching and managing external processes.
The existing Foundation API for spawning a process,
NSTask, originated in Objective-C. It was subsequently renamed toProcessin Swift. As the language has continued to evolve,Processhas not kept up. It lacks support forasync/await, makes extensive use of completion handlers, and uses Objective-C exceptions to indicate developer error. This proposal introduces a new package calledSubprocess, which addresses the ergonomic shortcomings ofProcessand enhances the experience of using Swift for scripting and other areas such as server-side development.
It’s currently available as a package, rather than being built into the OS, so the API may not be fully stable, but you can use it on older OS versions.
One of the issues with NSTask/Process is that the simple API works for small amounts of input/output but (unbeknownst to many, even some experts) hangs once you exceed the OS’s buffer size. There are ways around this, but they are awkward (though you can hide them in a  helper class). As Christian Tietze (Mastodon) writes:
Helge Heß pointed out that naive usage of
Pipein childProcesses can break your program if you pipe too much data.[…]
If the data is larger than the pipe buffer, you need to drain the corresponding
FileHandlewith repeated read calls. (Or provide data larger than 64KiB with repeated write calls, respectively.)If you try to send/receive the whole buffer in one go, from a user’s perspective, your program will freeze, and the read call never return. As a CLI app, it’ll never terminate.
Subprocess seems to be designed to transparently handle this. You can specify the output size limit (and the encoding, if you want a string output), and it will collect the pieces of output as they arrive.
I’ve been using Swift to write scripts, and scripts often need to run shell commands. Unfortunately, Subprocess is currently cumbersome to use for scripting, as Jacob Bartlett writes:
These simple scripts have ludicrous amounts of overhead for what are trivial one-liner operations in the bash shell. Bear with me, because later we’ll look at where Swift subprocess can actually work well, in a more complex workflow.
The additional overhead of requiring a full SwiftPM project, compared to a Bash script, makes it incredibly cumbersome for simple workloads. Also, it’s still not an actual script, so it’ll always require a compilation (and potentially dependency resolution) overhead whenever your code changes.
On the other hand, the syntax, type safety, and composability of Swift code work pretty nicely when you have complex automation workflows to orchestrate, build, and run on demand.
Previously:







