use std::ffi::OsStr;
use std::sync::{Mutex, MutexGuard, OnceLock};
static ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
fn raw_lock() -> MutexGuard<'static, ()> {
ENV_LOCK
.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[must_use = "EnvGuard releases the lock when dropped; bind it to a local"]
pub struct EnvGuard(#[allow(dead_code)] MutexGuard<'static, ()>);
impl EnvGuard {
#[expect(
unsafe_code,
reason = "guard serializes all env mutators, so no concurrent access"
)]
pub fn set_var(&self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) {
unsafe {
std::env::set_var(key, value);
}
}
#[expect(
unsafe_code,
reason = "see set_var — guard serializes all env mutators"
)]
pub fn remove_var(&self, key: impl AsRef<OsStr>) {
unsafe {
std::env::remove_var(key);
}
}
pub fn restore_var<T: AsRef<OsStr>>(&self, key: &str, previous: Option<T>) {
match previous {
Some(value) => self.set_var(key, value),
None => self.remove_var(key),
}
}
}
pub fn lock() -> EnvGuard {
EnvGuard(raw_lock())
}
pub fn set_var(key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) {
lock().set_var(key, value);
}
pub fn remove_var(key: impl AsRef<OsStr>) {
lock().remove_var(key);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_and_remove_roundtrip() {
let env = lock();
env.set_var("VTCODE_ENV_LOCK_TEST", "value-a");
assert_eq!(
std::env::var("VTCODE_ENV_LOCK_TEST").as_deref(),
Ok("value-a")
);
env.remove_var("VTCODE_ENV_LOCK_TEST");
assert!(std::env::var("VTCODE_ENV_LOCK_TEST").is_err());
}
#[test]
fn restore_var_restores_previous_value() {
let env = lock();
env.set_var("VTCODE_ENV_LOCK_RESTORE", "original");
let previous = std::env::var_os("VTCODE_ENV_LOCK_RESTORE");
env.set_var("VTCODE_ENV_LOCK_RESTORE", "temporary");
env.restore_var("VTCODE_ENV_LOCK_RESTORE", previous);
assert_eq!(
std::env::var("VTCODE_ENV_LOCK_RESTORE").as_deref(),
Ok("original")
);
env.remove_var("VTCODE_ENV_LOCK_RESTORE");
}
#[test]
fn restore_var_removes_when_previous_is_none() {
let env = lock();
env.set_var("VTCODE_ENV_LOCK_RESTORE_NONE", "temporary");
env.restore_var::<&str>("VTCODE_ENV_LOCK_RESTORE_NONE", None);
assert!(std::env::var("VTCODE_ENV_LOCK_RESTORE_NONE").is_err());
}
}