Remove experimental --env-set option
Project: rust-lang/rust | Language: Rust | Concepts: backwards-compatibility, configuration-management, refactoring | Difficulty: mid
Link: https://github.com/rust-lang/rust/pull/161831
The problem
The Rust compiler had an unstable --env-set command-line option that allowed users to inject environment variables at compile time, overriding the real environment for the env! and option_env! macros. This feature was experimental, had little adoption, and was considered insufficient for intended use cases. Its presence complicated the codebase and documentation.
Why it's non-trivial
Simply removing the option requires careful updates across the codebase: parsing logic, macro expansion, proc-macro bridges, documentation, and tests all referenced this feature. A naive removal could leave dead code, break macro behavior, or fail to update documentation and tests, leading to confusion or subtle bugs.
The approach
1. Remove the logical environment override from macro expansion:
Previously, the macro expansion logic checked for an override in cx.sess.opts.logical_env before falling back to the real environment:
-fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> {
- let var = var.as_str();
- if let Some(value) = cx.sess.opts.logical_env.get(var) {
- return Ok(Symbol::intern(value));
- }
- // If the environment variable was not defined with the `--env-set` option, we try to retrieve it
- // from rustc's environment.
- Ok(Symbol::intern(&env::var(var)?))
-}
+fn lookup_env(var: Symbol) -> Result<Symbol, VarError> {
+ let var = var.as_str();
+ // Try to retrieve the environment variable from rustc's environment.
+ Ok(Symbol::intern(&env::var(var)?))
+}
This change removes the check for logical_env, so only the real environment is used.
2. Remove the option from command-line parsing and configuration:
The Options struct and CLI parsing no longer track or parse --env-set:
- logical_env: FxIndexMap::default(),
and
- opt(Unstable, Multi, "", "env-set", "Inject an environment variable", "<VAR>=<VALUE>"),
This removes the logical_env field and the CLI flag definition.
3. Remove parsing and handling code for the flag:
-fn parse_logical_env(
- early_dcx: &EarlyDiagCtxt,
- matches: &getopts::Matches,
-) -> FxIndexMap<String, String> {
- let mut vars = FxIndexMap::default();
-
- for arg in matches.opt_strs("env-set") {
- if let Some((name, val)) = arg.split_once('=') {
- vars.insert(name.to_string(), val.to_string());
- } else {
- early_dcx.early_fatal(format!("`--env-set`: specify value for variable `{arg}`"));
- }
- }
-
- vars
-}
All code for parsing and storing --env-set is deleted.
4. Remove proc-macro bridge support for injected environment:
- fn injected_env_var(var: &str) -> Option<String>;
and
- fn injected_env_var(&mut self, var: &str) -> Option<String> {
- self.ecx.sess.opts.logical_env.get(var).cloned()
- }
This removes the bridge method and its implementation, so proc-macros can no longer see injected env vars.
5. Remove documentation and tests for the feature:
- The unstable book page for
--env-setis deleted. - All tests specifically for
--env-setare removed or rewritten to userustc-envinstead.
Example test rewrite:
-//@ compile-flags: --env-set FOO=123abc -Zunstable-options
+//@ rustc-env: FOO=123abc
From the review thread
[issue] bonzini: > Why does an unstable option not have a -Z prefix?
Because it's enabled by -Zunstable-options. :)
[review] nnethercote: This
--envwas out of date.
Takeaways
- Remove all supporting code, documentation, and tests when deprecating a feature to avoid dead code and user confusion.
- Ensure that macro and proc-macro interfaces are updated consistently when changing how environment or configuration is handled.
- When removing experimental or unstable features, check for real-world usage and coordinate with stakeholders or downstream users.