Accessing C global variable with Swift 6 strict Concurrency

Linux Musl & Android
Getting this error while trying to access environ values:

@inlinable nonisolated(unsafe)
internal var pointer: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>? {
    environ
    `- error: reference to var 'environ' is not concurrency-safe because it involves shared mutable state
}

posix_unistd.environ:1:12: note: var declared here
public var environ: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>!
           `- note: var declared here

The only solution I came to is to make C function wrapper returning environ. Is there any solution to do this in Swift way?

Are you looking for an answer that generically answers this question or one that address fetching the environment because ProcessInfo.processInfo.environment["KEY"] is something you could use, maybe.

I used the existing C functions in my little thing for the environment specifically. Works in Swift 6 mode.

A more generic answer I'll have to dig for! ETA(But yes, writing accessor/bridging code is pretty common for me interacting with C, if only so I can slowly replace it with Swift more easily over time)

ETA: For posterity incase the link rots

import Foundation

//From APIng 
//https://github.com/carlynorama/APIng/blob/2a4e3620eaff902254d33e103b08ea832e6bc134/Sources/APIng/DotEnv.swift
public enum DotEnv {
    
    //should prefer ProcessInfo.processInfo.environment["KEY"]
    public static func getEnvironmentVar(_ key: String) -> String? {
        guard let rawValue = getenv(key) else { return nil }
        return String(utf8String: rawValue)
    }
    public static func setEnvironment(key:String, value:String, overwrite: Bool = false) {
        setenv(key, value, overwrite ? 1 : 0)
    }
    
    public static func loadDotEnv() throws {
        if let envString = try? String(contentsOf: URL(fileURLWithPath: ".env")) {
            loadIntoEnv(envString)
        } else {
            fatalError("can't find .env file.")
        }
    }
    
    public static func loadSecretsFile(url:URL) throws {
        //let url = URL(fileURLWithPath: ".env")
        guard let envString = try? String(contentsOf: url) else {
           fatalError("no env file data")
        }
        loadIntoEnv(envString)
    }
    
    private static func loadIntoEnv(_ envString:String) {
        envString
            .trimmingCharacters(in: .newlines)
            .split(separator: "\n")
            .lazy //may or may not save anything
            .filter({$0.prefix(1) != "#"})  //is comment
            .map({ $0.split(separator: "=").map({String($0.trimmingCharacters(in: CharacterSet(charactersIn:"\"\'")))}) })
            .forEach({  addToEnv(result: $0) })

        func addToEnv(result:Array<String>) {
            if result.count == 2  {
                //print(result[0], result[1])
                setEnvironment(key: result[0], value: result[1], overwrite: true)
            } else {
                //item would of had to have contained more than 1 "=" or none at all. I'd like to know about that for now.
                print("Failed dotenv add: \(result)")
            }
        }
    }
    

}

I'm more interested in how to overcome such conditions when there are global var in C and strict Concurrency, environ was as bright example.

p.s. I develop Foundation-less app and libraries, ProcessInfo.processInfo.environment is unavailable for me.

1 Like

environ was as bright example.

It’s hard to answer this without more information about your constraints. Your original example mentioned environ, and that comes with certain thread safety rules on the C side [1]. However, the above makes it clear that environ was just an example.

So, focusing on the real case, what threading guarantees do you have on the C side of things?

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

[1] AFAICT those rules are “It’s not safe and never can be.” While getenv and setenv have internal locking, at least on Darwin, there’s no way that environ can participate in that.

1 Like