Hello, Swift community.
The review of SE-0529: FilePath was initially scheduled to run from April 22nd through May 4th, 2026. Over the course of the review, several rounds of amendments were made to the proposal: first here, then here. To accommodate discussion of those changes, and to avoid cutting short some productive debates about other points, the review was informally extended for quite a while past its original scheduled conclusion.
The community's feedback was generally positive on the basic idea of adding a type that represents a file path on the current system. The LSG generally agrees that this is a good direction for the standard library. There were some objections in the thread about specific parts of the design, many of which were settled by the amendments or by discussion in the review. I'd like to share the LSG's thinking about a few issues in particular.
During the review, there was a lot of discussion about the portability of certain APIs, such as driveLetter. Since a FilePath represents a path on the current system, and these APIs are presenting non-portable concepts that only really make sense on specific systems, the LSG asked the authors to make these APIs platform-conditional for now. Programmers writing unportable code will not be bothered by this, since platform conditionality is not enforced except by building for different platforms. Meanwhile, programmers trying to write portable code will have their attention called to places where their logic is assuming a specific target. If we decide that the API really would be useful to portable programmers, we can always make it unconditionally available (or introduce a more generic, portable API) later.
There was also a lot of debate about the proposed resolve method, which we believe can be distilled to three principal objections:
-
resolvewould be the first I/O operation in the standard library[1]. The objectors ask that we wait to introduce it until it can part of a more complete I/O library.The LSG notes that a more complete I/O library will depend on a lot of new types and idioms that will have to be developed in the library.
resolveis different because it really does just involve thisFilePathtype. We do not feel like this is a strong reason by itself to delay addingresolve. -
resolvecan be misused, and in some situations (generally when the code has more privileges than its clients) this can lead to security bugs through TOCTOU logic errors. The correct fix for these bugs is often to stop trying to resolve the path in the first place. The objectors ask that we not add the API at all given this, or that we only add it in some different form, e.g. one that holds a kernel handle to maintain a stable reference to the resolved file.The LSG acknowledges that
resolvehas these problems. However, there are also appropriate ways to useresolve, e.g. combined with appropriate flags passed toopen[2]. The LSG is satisfied that the documentation can adequately inform programmers about how and when to use this API correctly. Not including this functionality is also likely to lead programmers to naively recreate it with manual path manipulation, which can be its own source of bugs, including security bugs.The suggested alternate forms of this API, e.g. using
O_PATH-like functionality, are far from being drop-in replacements, and they are not yet portably implementable. -
Finally,
resolveis a potentially blocking API. The objectors feel that it should beasync.The LSG acknowledges this as a tricky problem. It is true that
resolvecan require waiting for arbitrary I/O to finish. Even the fastest modern SSDs, with latencies measured in the tens of microseconds, are slow enough relative to the CPU that the current thread could usually be usefully employed doing other work in the meantime; and of course filesystems are often backed by much slower technologies, including older NANDs, spinning disks, and network servers near and far. All of that naturally suggests that this API ought to beasyncon some semantic level.[3]However, operating systems do not currently provide ways to asynchronously resolve paths. The only way this operation could be implemented asynchronously at present is with an I/O worker pool that performs the normal, blocking syscall. The LSG is making a pragmatic choice here. We are unwilling to delay offering this API for an unbounded period of time, when it can be used safely (if carefully) in many situations and offers substantial security improvements over more naive path manipulation techniques, until either operating systems introduce async resolve or Swift introduces I/O workers for some other reason.
SE-0529 is therefore accepted with the modifications made over the course of the review.
I'd like to thank the Swift community for its thoughtful and patient participation in this rather drawn-out review process. Your contributions really do help to make Swift a better language.
John McCall
Review Manager
Ignoring
printandreadLine, which have somewhat special roles in the library; they are specialized for their simple purposes and aren't really models for what general I/O routines in Swift are likely to look like. ↩︎Such as
openat2'sRESOLVE_NO_SYMLINKSon Linux, orO_NOFOLLOW_ANYon Apple platforms (although this is unnecessary becauseresolvewill also add/.nofollowto the path on Apple platforms). ↩︎The LSG acknowledges the argument that blocking can also be semantically problematic in specific applications. For example, when implementing a filesystem in userspace, blocking on another filesystem request can result in deadlock. The LSG does not feel strongly motivated by this argument; there are many other ways to run into this kind of deadlock in such services, and programmers writing them simply have to be aware of the restrictions on them. ↩︎