Hello all, I've written a second draft for inclusion of FilePath in the stdlib. I've trimmed many of the syntactic operations, focusing on what is essential to get in Swift: the types, fundamental conformances, and behavior fixes (like trailing slash). Future proposals can include many of the convenience methods.
Full proposal viewable at FilePath in the stdlib - pitch 2 · GitHub
Add FilePath to the Standard Library
- Proposal: TBD
- Authors: Michael Ilseman, Saleem Abdulrasool
- Review Manager: TBD
- Status: Pitch
- Implementation: TBD
- Review: TBD
Introduction
We propose adding FilePath and its essential operations to the Swift standard library. FilePath parses platform-specific path syntax on the developer's behalf, provides a normalized view of path components, and enables resolution against the filesystem. This proposal focuses on establishing the core type, its conformances, and the essential operations needed to adopt it as a currency type. Additional syntactic convenience methods and platform-specific path decomposition API are planned as future additions. See Future Directions.
Motivation
Swift has no standard representation for file system paths. The swift-system package introduced FilePath in System 0.0.1, and it has since gained a comprehensive set of syntactic operations for platform-correct path manipulation. But because FilePath lives in an external package, it cannot be depended on by the standard library or the Swift runtime, nor can it appear in API in toolchain libraries such as Foundation.
Every new API that needs to name a file path faces the same dilemma around using String that SE-0513 faces. String is a poor fit for paths as it does not capture the structure of a file path. Path string representations are also platform-specific and path components are not necessarily valid Unicode.
FilePath stores the path in its native platform encoding, enabling strongly-typed programming with paths. It encapsulates the platform-specific details of path syntax (separators, root/prefix forms, special components) so that developers can work with paths correctly without needing to understand each platform's conventions.
Proposed solution
We propose adding FilePath, FilePath.Component, and FilePath.ComponentView to the Swift module, alongside essential functionality for construction, decomposition, resolution, and C interoperability.
var path: FilePath = "/var/www/static/index.html"
path.isAbsolute // true
path.hasTrailingSeparator // false
for component in path.components {
print(component, component.kind) // "var": .regular, "www": .regular, ...
}
// Resolve against the filesystem
let resolved = try path.resolve()
// Construct a path through mutation
var config: FilePath = "/etc/nginx"
config.components.append("nginx.conf")
// config is "/etc/nginx/nginx.conf"
Detailed design
See full proposal
Source compatibility
All changes are additive.
SystemPackage.FilePath and/or System.FilePath can migrate to the standard library's FilePath via a conditional typealias:
#if compiler(>=6.4) // or whichever version this lands in
public typealias FilePath = Swift.FilePath
#else
public struct FilePath { ... }
#endif
Existing functionality from SystemPackage.FilePath (such as syntactic convenience methods) can be added as extensions on Swift.FilePath on toolchain versions that include this change. This enables a smooth source-compatible migration path.
ABI compatibility
This proposal is purely an extension of the ABI of the standard library and does not change any existing features.
On Darwin, System.FilePath currently has ABI commitments. Migration from the System module on Darwin can be handled through ABI-level redirection so that existing binaries linked against System.FilePath continue to work.
Implications on adoption
Adopters will need a toolchain that includes this change. The type cannot be back-deployed to older runtimes without additional work.
For existing users of swift-system, SystemPackage (the SwiftPM package) can use #if conditionals as described above. System (the Darwin framework) can perform ABI migration, redirecting the existing System.FilePath symbol to the standard library implementation, preserving binary compatibility for existing Darwin binaries.
Future directions
Syntactic operations and path decomposition
Future API for FilePath could include additional syntactic operations and more path decomposition, such as originally pitched. This includes convenience accessors like lastComponent, stem, extension, removingLastComponent(), and mutation methods like append, push, removePrefix. These can be expressed in terms of the ComponentView and are back-deployable.
Lexical resolution and resolve-beneath
Lexical operations that collapse .. components without consulting the filesystem, and operations that ensure a subpath does not escape a base directory, are important for sandboxing and security-sensitive code. These were part of the original pitch and are planned as future additions. Real resolution (via resolve()) is provided in this proposal to ensure developers have the correct tool available from day one.
Path prefix type and analysis
Windows path prefixes and Darwin roots carry more semantic content than Linux. A future FilePath.Prefix type could expose this structure: UNC server/share decomposition, XNU resolve flags, Windows volume identifiers, and more. The isAbsolute, isRooted, and driveLetter queries in this proposal cover the most common needs while the design of a full prefix type matures.
Platform string APIs and CInterop
swift-system defines a CInterop namespace with typealiases for platform-specific character types (PlatformChar, PlatformUnicodeEncoding) and provides init(platformString:) APIs on FilePath and its subtypes. This proposal provides withCString/withWCString for passing paths to C APIs, and Span-based access on components. Future work could include a PlatformChar typealias and more functionality along these lines.
Path parsing APIs and verbatim storage modes
Future work includes exposing FilePath's internal parser for uses over borrowed storage (such as Span), allowing developers to parse paths in-place without copying into a FilePath.
Some use cases (logging, diagnostics) benefit from preserving the exact bytes of a path as originally provided, without any normalization. These would be better handled by referring to the actual original, possibly facilitated with path parsing API over borrowed storage. But, if it proves to be an essential use case, FilePath as pitched is sufficiently resilient that we could add a verbatim storage mode in the future.
SystemString
swift-system internally uses a SystemString type that handles the underlying storage for FilePath. A similar type may be independently useful as a public type for working with null-terminated platform-encoded strings with opaque contents.
More path types and filesystem-specific functionality
Future work could include a paths library or package that adds:
- Type-enforced invariants:
AbsolutePath,ResolvedPath, etc. - Platform-specific foreign path types:
XNUPath,Win32Path,WinNTPath, etc. - File-system specific (and configuration specific) functionality over components: case conversion, Unicode normalization, etc.
Testing Windows path behavior on Linux/macOS (and vice versa) is a known gap that platform-specific path types could address; these could conform to a common protocol while each implementing the semantics of its target platform.
Legacy device name handling
On Windows, legacy device names (CON, NUL, COM1, etc.) receive special treatment from the Win32 layer, and their exact handling varies across Windows versions. Future work could add detection or mitigation for these names, potentially as part of a resolve-beneath or sandboxing API.
Alternatives considered
Do more in Swift 6.4
The original pitch included a FilePath.Root type, convenience methods (lastComponent, stem, extension, starts(with:), append, push), and lexical resolution operations. We chose to defer these in favor of shipping the essential core: the type, its conformances, the component view, and real resolution. The deferred API can be added as extensions on FilePath and is back-deployable where expressed in terms of the component view. Shipping the type and conformances first is critical because those cannot be added retroactively without migration costs (e.g. someone conforming FilePath to Hashable themselves).
Include Codable conformance
FilePath intentionally does not conform to Codable. Serialized paths are inherently platform-specific: a path serialized on Unix is not meaningful on Windows. Path prefix semantics can also change across OS versions (Windows 10 and 11 differ in device name recognition; Darwin may add new resolve flag prefixes). The existing Codable conformance on System.FilePath uses an awkward binary encoding that has been a source of friction. Rather than commit the standard library to a serialization format, we leave serialization to application-level code that can choose a format appropriate to its needs. String(decoding:) and init(_ string:) provide the necessary conversion.
Do more: bring all of swift-system into the toolchain
As discussed in system-in-the-toolchain, it may also make sense to have a System module in the toolchain for low-level OS interfaces and low-level currency types (like FileDescriptor, Errno, etc).
FilePath is different from these other types in that it transcends the entire tech stack, from kernel-level programming to high level scripts and automation. Note that we are not pulling in syscalls such as FilePath.stat, those will remain in System/SystemPackage.
Add FilePath to a separate standard library module
FilePath could live in a new module (e.g. FilePaths, Path, Files, ...) that ships with the toolchain but requires an explicit import. Currency types lose much of their value when they require an import. String, Array, Int, and Result are all in the Swift module; it is our (weakly held) opinion FilePath should be too.
Use Foundation's URL
Foundation's URL is designed for URI semantics, including scheme parsing and percent-encoding. File system paths and URIs have different structure and different invariants. For example, URL(fileURLWithPath:) and URL.appendingPathComponent make blocking file system calls, which is surprising for what appears to be a pure data type. On Unix, paths containing bytes that are not valid UTF-8 cannot survive conversion to a file:// URL, which requires percent-encoding. Foundation also sits high in the dependency stack; the Swift runtime and toolchain components cannot depend on it.
Do nothing
Every new API that needs to name a file will continue using String, perpetuating the loss of structure, platform correctness, and type safety that FilePath was designed to address.
Acknowledgments
Thanks to Saleem Abdulrasool for co-authoring the original FilePath syntactic operations design and implementation in swift-system. Thanks to the participants in the System-in-toolchain discussion, the SE-0513 review, and the first pitch thread for helping shape this proposal.