bigywater.tech
Rust · 147

环境变量读取

环境变量的读取看似一行代码,却牵出编码、可变性、进程边界与编译期差异。这篇逐条验证 env::var 的各种结果、set_var 为什么变成 unsafe、子进程的修改是否可见,以及 env! 与 env::var 的区别。

NotUnicode
环境变量值不是合法 UTF-8 时 env::var 的错误
E0133
2024 edition 里不加 unsafe 调用 set_var 的错误码
编译期 与 运行期
option_env! 读编译时的值,env::var 读运行时的值

读取

实测 · 本机执行输出
  U147_SET      var = Ok("hello")   var_os = Some("hello")
  U147_MISSING  var = Err("environment variable not found")   var_os = None
  U147_BAD      var = Err("environment variable was not valid unicode: \"bad\\xFF\\xFE\"")   var_os = Some("bad\xFF\xFE")
  U147_EMPTY    var = Ok("")   var_os = Some("")
  U147_EMPTY 是空字符串不是不存在:Ok(true)
  var_os 比 var 能装下非 UTF-8 的值:Some([98, 97, 100, 255, 254])

子进程如何继承与修改环境见子进程与 Command。

修改

实测 · 本机执行输出
  源码:
      1 | use std::{env, process::Command};
      2 | fn main() {
      3 |     unsafe { env::set_var("U147_X", "from-rust"); }
      4 |     let o = Command::new("sh").arg("-c").arg("echo child sees U147_X=$U147_X").output().unwrap(); println!("set_var 之后子进程看到:{}", String::from_utf8_lossy(&o.stdout).trim());
      5 |     unsafe { env::remove_var("U147_X"); }
      6 |     let o = Command::new("sh").arg("-c").arg("echo child sees U147_X=[$U147_X]").output().unwrap(); println!("remove_var 之后子进程看到:{}", String::from_utf8_lossy(&o.stdout).trim());
      7 | }
  编译:成功
  运行(退出码 0):
    set_var 之后子进程看到:child sees U147_X=from-rust
    remove_var 之后子进程看到:child sees U147_X=[]
  源码:
      1 | fn main() { std::env::set_var("A", "1"); }
  编译:失败
    error[E0133]: call to unsafe function `set_var` is unsafe and requires unsafe block
     --> <实验目录>/src/u147c.rs:1:13
      |
    1 | fn main() { std::env::set_var("A", "1"); }
      |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
      |
      = note: consult the function's documentation for information on how to avoid undefined behavior
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0133`.

unsafe 的含义见unsafe 的安全边界。

子进程的修改

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    child: child
    子进程改完之后,父进程仍然是 Ok("parent")

遍历

实测 · 本机执行输出
  环境里放一个非 UTF-8 的变量:vars → 退出码 101,stdout '',stderr ["thread 'main' (7496955) panicked at /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library/std/src/env.rs:168:83:", 'called `Result::unwrap()` on an `Err` value: "bad\\xFF\\xFE"']
  环境里放一个非 UTF-8 的变量:vars_os → 退出码 0,stdout 'vars_os() 共 58 个',stderr []

编译期

实测 · 本机执行输出
  编译时设置 A,运行时设置 B:编译期 option_env! = Some("A-at-compile");运行期 env::var = Some("B-at-run")
  编译时没设置,运行时设置 B:编译期 option_env! = None;运行期 env::var = Some("B-at-run")
  env!("U147_REQUIRED") 编译期缺失:['error: environment variable `U147_REQUIRED` not defined at compile time']

build.rs 里的 cargo:rustc-env 见Build Script build.rs。

分步演示

环境变量
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。非 UTF-8 值通过 Python 的 bytes 环境传入
    • 没有验证:多线程中并发 set_var 与 getenv 的真实竞争后果、Windows 上环境变量名大小写不敏感、dotenv 类 crate、env::args 与环境变量的关系、setenv 在 C 库里的线程安全性
    京ICP备2026067502号-1京公网安备11010502063487号