Attributes come in two types: inert (or built-in) and active (non-builtin).

Builtin/inert attributes

These attributes are defined in the compiler itself, in compiler/rustc_feature/src/

Examples include #[allow] and #[macro_use].

These attributes have several important characteristics:

  • They are always in scope, and do not participate in typical path-based resolution.
  • They cannot be renamed. For example, use allow as foo will compile, but writing #[foo] will produce an error.
  • They are 'inert', meaning they are left as-is by the macro expansion code. As a result, any behavior comes as a result of the compiler explicitly checking for their presence. For example, lint-related code explicitly checks for #[allow], #[warn], #[deny], and #[forbid], rather than the behavior coming from the expansion of the attributes themselves.

'Non-builtin'/'active' attributes

These attributes are defined by a crate - either the standard library, or a proc-macro crate.

Important: Many non-builtin attributes, such as #[derive], are still considered part of the core Rust language. However, they are not called 'builtin attributes', since they have a corresponding definition in the standard library.

Definitions of non-builtin attributes take two forms:

  1. Proc-macro attributes, defined via a function annotated with #[proc_macro_attribute] in a proc-macro crate.
  2. AST-based attributes, defined in the standard library. These attributes have special 'stub' macros defined in places like library/core/src/macros/

These definitions exist to allow the macros to participate in typical path-based resolution - they can be imported, re-exported, and renamed just like any other item definition. However, the body of the definition is empty. Instead, the macro is annotated with the #[rustc_builtin_macro] attribute, which tells the compiler to run a corresponding function in rustc_builtin_macros.

All non-builtin attributes have the following characteristics:

  • Like all other definitions (e.g. structs), they must be brought into scope via an import. Many standard library attributes are included in the prelude - this is why writing #[derive] works without an import.
  • They participate in macro expansion. The implementation of the macro may leave the attribute target unchanged, modify the target, produce new AST nodes, or remove the target entirely.