When adding/updating configuration handling, resolve configuration once into a single immutable runtime context, and make precedence/fallback behavior explicit.
Rules
try! for config loading. Prefer throws at the boundary and fail fast with a clear error message.environment variables -> user overrides file -> system config.
Sketch
struct RuntimeConfigContext: Sendable {
let finalConfig: ContainerSystemConfig
}
enum ConfigError: Error {
case loadFailed(String)
}
func resolveRuntimeConfig() throws -> RuntimeConfigContext {
do {
let system = try SystemRuntimeOptions.loadConfig() // throws, no try!
// precedence: env -> user file -> system
let userOverrides = try loadUserOverrides() // throws
let envOverrides = loadEnvOverrides() // evaluate now
let merged = merge(system: system, user: userOverrides, env: envOverrides)
return RuntimeConfigContext(finalConfig: merged)
} catch {
throw ConfigError.loadFailed("Failed to resolve runtime config: \(error)")
}
}
// Inject at boundaries
let ctx = try resolveRuntimeConfig()
startServices(with: ctx) // services/utilities can use ctx.finalConfig without plumb-through
Apply these rules to new config flows and refactors: they prevent unsafe initialization, reduce coupling, make override behavior predictable, and keep platform-specific concerns contained.