swift-foundations/swift-environment
Process environment variable access with thread-safe reads and writes, immutable snapshots, and task-local overlays for isolated overrides.
Key Features
- Process environment access — read a single variable, enumerate all variables, or check presence with
Environment.read(...),Environment.read.all(), andEnvironment.read.isSet(...). - Thread-safe reads and writes — every operation is serialized through a process-global mutex, so concurrent callers that use this API never race on
getenv/setenv. - Task-local overlays —
Environment.withOverlay([...])layers overrides onto the current scope and its child tasks without mutating real process state, giving each test or task an isolated view. - Immutable snapshots —
Environment.Snapshotcaptures the environment at a point in time;mergederives modified copies where anilvalue removes a key, ready to hand to a spawned child process. - Typed throws — writes fail with a typed
Kernel.Environment.Error, so noany Errorcrosses the API surface. - Windows-aware enumeration —
Environment.read.all()automatically excludes Windows' internal=-prefixed pseudo-variables.
Quick Start
Environment.withOverlay layers override values onto a scope and its child tasks without touching real process state — so a test or task can run against a modified environment while concurrent work, and the live process, see the original values. Doing this with raw setenv mutates global state and is not safe across concurrent callers.
import Environment
// Reads hit the real process environment and are thread-safe.
let realLevel = Environment.read("LOG_LEVEL") // e.g. nil
// withOverlay layers values on top for a scope, without mutating
// global process state. The overlay is task-local, so concurrent work
// and the real environment are unaffected.
let scopedLevel = Environment.withOverlay(["LOG_LEVEL": "debug"]) {
Environment.task.read("LOG_LEVEL") // "debug" — the overlay wins
}
print(realLevel ?? "unset", scopedLevel ?? "unset") // unset debugTo build an environment for a spawned child process, snapshot the current state and derive a modified copy. A nil value removes a key:
import Environment
let base = Environment.Snapshot.current()
let childEnvironment = base.merge([
"LOG_LEVEL": "debug", // set
"TMPDIR": nil // remove
])Installation
dependencies: [
.package(url: "https://github.com/swift-foundations/swift-environment.git", branch: "main")
].target(
name: "YourTarget",
dependencies: [
.product(name: "Environment", package: "swift-environment")
]
)Requires Swift 6.3.1 and macOS 26 / iOS 26 / tvOS 26 / watchOS 26 / visionOS 26.
Error Handling
Parsing a dotenv document throws a typed Environment.Dotenv.Error; every case carries the 1-based physical line it was detected on:
Environment.Dotenv.Error
├── .invalidKey(line:) // key is empty or begins with a non-key byte
├── .missingSeparator(line:) // a key line has no `=` (or trailing garbage)
├── .unterminatedQuote(line:) // an opened quote never closed before EOF
└── .invalidEscape(line:) // `\` in a double-quoted value followed by an
// unsupported bytedo {
let dotenv = try Environment.Dotenv(parsing: text)
_ = dotenv
} catch .invalidKey(let line) {
_ = line
} catch .missingSeparator(let line) {
_ = line
} catch .unterminatedQuote(let line) {
_ = line
} catch .invalidEscape(let line) {
_ = line
}Process-environment reads surface a separate typed Kernel.Environment.Error.
Community
Discussion thread will be created at first public release.
License
Apache 2.0. See LICENSE.
Package Metadata
Repository: swift-foundations/swift-environment
Default branch: main
README: README.md