← Back to index
PR #3796Work-in-progress preview from an open pull request.View on GitHub ↗
REVIEW
#3796

cfg_logical_ops

Authorjhpratt
CreatedMar 30 2025
UpdatedJun 21 2026
Rust Issue

#[cfg], #[cfg_attr], and cfg!() can use &&, ||, and ! for all, any, and not, respectively. Due to precedence, feature = "foo" must be parenthesized when adjoining any of the new operators.

Motivation

While there are no technical restrictions to using logical operators, this was not always the case. In Rust 1.0, attributes could not contain arbitrary tokens. This restriction was lifted in Rust 1.34, but the cfg syntax was not updated to take advantage of this. By letting developers use logical operators, we are lessening the burden of having to remember the cfg syntax.

Explanation

#[cfg(foo && bar)] enables the annotated code if and only if both foo and bar are enabled. Similarly, #[cfg(foo || bar)] enables the annotated code if and only if either foo or bar is enabled. Finally, #[cfg(!foo)] enables the annotated code if and only if foo is not enabled. #[cfg_attr] and cfg!() behave the same way.

Precedence is the same as in expressions, which necessitates using parentheses in some situations. This is shown in the table below. = is not treated the same as == for precedence purposes; it has lower precedence than all logical operators.

Examples

SyntaxEquivalent toRationale
a && ball(a, b)definition of &&
a || bany(a, b)definition of ||
!anot(a)definition of !
(a)adefinition of ()
a && b && c && dall(a, b, c, d) (or all(all(all(a, b), c), d))&& is associative
a || b || c || dany(a, b, c, d) (or any(any(any(a, b), c), d))|| is associative
!!!!!!anot(not(not(not(not(not(a))))))! can be repeated
((((((a))))))a() can be nested
a && b || c && dany(all(a, b), all(c, d))|| has lower precedence than &&
a || b && c || dany(a, all(b, c), d)|| has lower precedence than &&
(a || b) && (c || d)all(any(a, b), any(c, d))() can be used for grouping
!a || !b && !cany(not(a), all(not(b), not(c)))! has highest precedence
feature="foo" || feature="bar"syntax error|| has higher precedence than =, which may be confusing, so we ban this syntax
(feature="foo") || (feature="bar")any(feature="foo", feature="bar")use () for grouping
feature="foo" && feature="bar"syntax error&& has higher precedence than =, which may be confusing, so we ban this syntax
(feature="foo") && (feature="bar")all(feature="foo", feature="bar")use () for grouping
!feature="foo"syntax error! has higher precedence than =, which may be confusing, so we ban this syntax
!(feature="foo")not(feature="foo")use () for grouping
!all(x, y)not(all(x, y))! has lower precedence than "function call"
any(!x || !w, !(y && z))any(any(not(x), not(w)), not(all(y, z)))!, && etc. can be used inside any, all and not
true && !falseall(true, not(false))!, && etc. can be used on boolean literals (they are syntactically identifiers)
!accessible(std::mem::forget)not(accessible(std::mem::forget))!, && etc. can be used on cfg_accessible
accessible(std::a || std::b)syntax error… but not inside
!version("1.42.0")not(version("1.42.0"))!, && etc. can be used on cfg_version
version(!"1.42.0")syntax error… but not inside

Formal syntax

[cfg.syntax] is changed to the following:

Syntax
ConfigurationPredicate :
      ConfigurationOption
   | ConfigurationAll
   | ConfigurationAny
   | ConfigurationNot
   | ConfigurationAnd
   | ConfigurationOr
   | ConfigurationNegation
   | ( ConfigurationPredicate )

ConfigurationNegatable :
   ConfigurationOptionIdent
   | ConfigurationAll
   | ConfigurationAny
   | ConfigurationNot
   | ConfigurationNegation
   | ( ConfigurationPredicate )

ConfigurationOptionIdent :
   [IDENTIFIER]

ConfigurationOption :
   [IDENTIFIER] (= ([STRING_LITERAL] | [RAW_STRING_LITERAL]))?

ConfigurationAll
   all ( ConfigurationPredicateList? )

ConfigurationAny
   any ( ConfigurationPredicateList? )

ConfigurationNot
   (not | !) ( ConfigurationPredicate )

ConfigurationAnd
   ConfigurationPredicate && ConfigurationPredicate

ConfigurationOr
   ConfigurationPredicate || ConfigurationPredicate

ConfigurationNegation
   ! ConfigurationNegatable

ConfigurationPredicateList
   ConfigurationPredicate (, ConfigurationPredicate)* ,?

All future function-like predicates (such as version and accessible) should be added to ConfigurationNegatable.

Drawbacks

  • Two ways to express the same thing. This can be somewhat mitigated by a lint for the old syntax.

Rationale and alternatives

  • The current syntax is verbose and a relic of the past when attributes could not contain arbitrary tokens.
  • Using existing, widely-understood operators makes the syntax more familiar.
  • & and | could be used instead of && and ||. Short-circuiting behavior is unobservable in this context, so the behavior would be the same.
  • feature != "foo" could be allowed as shorthand for !(feature = "foo"). This could plausibly be interpreted as "any feature except 'foo'", which is why it is not included in this proposal.

Prior art

The efg crate is nearly identical to this proposal, the sole difference being not requiring = for key-value pairs.

Unresolved questions

None so far.

Future possibilities

  • Pattern-like syntax such as #[cfg(feature = "foo" | "bar")] could be allowed as a shorthand for #[cfg((feature = "foo") || (feature = "bar"))]. This would be particularly useful for platform-specific code (e.g. #[cfg(target_os = "linux" | "windows")]).
  • The use of parentheses could be relaxed in some situations, such as allowing !feature = "foo" or feature = "foo" || feature = "bar".
  • A different syntax for key-value pairs, such as feature("foo"), could be used for clarity (as it is neither assignment nor equality) and to reduce the need for manual precedence management.