Macros

Guideline: Shall not use Declarative Macros gui_h0uG1C9ZjryA
status: draft
tags: reduce-human-error
category: mandatory
decidability: decidable
scope: system
release: todo

Description of the guideline goes here.

Rationale: rat_U3AEUPyaUhcb
status: draft
parent needs: gui_h0uG1C9ZjryA

Explanation of why this guideline is important.

Non-Compliant Example: non_compl_ex_Gb4zimei8cNI
status: draft
parent needs: gui_h0uG1C9ZjryA

Explanation of code example.

fn example_function() {
    // Non-compliant implementation
}
Compliant Example: compl_ex_Pw7YCh4Iv47Z
status: draft
parent needs: gui_h0uG1C9ZjryA

Explanation of code example

fn example_function() {
    // Compliant implementation
}
Guideline: Procedural macros should not be used gui_66FSqzD55VRZ
status: draft
tags: readability, reduce-human-error
category: advisory
decidability: decidable
scope: crate
release: 1.85.0;1.85.1

Macros should be expressed using declarative syntax in preference to procedural syntax.

Rationale: rat_AmCavSymv3Ev
status: draft
parent needs: gui_66FSqzD55VRZ

Procedural macros are not restricted to pure transcription and can contain arbitrary Rust code. This means they can be harder to understand, and cannot be as easily proved to work as intended. Procedural macros can have arbitrary side effects, which can exhaust compiler resources or expose a vulnerability for users of adopted code.

Non-Compliant Example: non_compl_ex_pJhVZW6a1HP9
status: draft
parent needs: gui_66FSqzD55VRZ

(example of a simple expansion using a proc-macro)

// TODO
Compliant Example: compl_ex_4VFyucETB7C3
status: draft
parent needs: gui_66FSqzD55VRZ

(example of the same simple expansion using a declarative macro)

// TODO
Guideline: A macro should not be used in place of a function gui_2jjWUoF1teOY
status: draft
tags: reduce-human-error
category: mandatory
decidability: decidable
scope: system
release: todo

Functions should always be preferred over macros, except when macros provide essential functionality that functions cannot, such as variadic interfaces, compile-time code generation, or syntax extensions via custom derive and attribute macros.


Rationale: rat_M9bp23ctkzQ7
status: draft
parent needs: gui_2jjWUoF1teOY

Macros are powerful but they come at the cost of readability, complexity, and maintainability. They obfuscate control flow and type signatures.

Debugging Complexity

  • Errors point to expanded code rather than source locations, making it difficult to trace compile-time errors back to the original macro invocation.

Optimization

  • Macros may inhibit compiler optimizations that work better with functions.

  • Macros act like #[inline(always)] functions, which can lead to code bloat.

  • They don’t benefit from the compiler’s inlining heuristics, missing out on selective inlining where the compiler decides when inlining is beneficial.

Functions provide

  • Clear type signatures.

  • Predictable behavior.

  • Proper stack traces.

  • Consistent optimization opportunities.

Non-Compliant Example: non_compl_ex_TZgk2vG42t2r
status: draft
parent needs: gui_2jjWUoF1teOY

Using a macro where a simple function would suffice, leads to hidden mutation:

macro_rules! increment_and_double {
    ($x:expr) => {
        {
            $x += 1; // mutation is implicit
            $x * 2
        }
    };
}
let mut num = 5;
let result = increment_and_double!(num);
println!("Result: {}, Num: {}", result, num);
// Result: 12, Num: 6

In this example, calling the macro both increments and returns the value in one go—without any clear indication in its “signature” that it mutates its argument. As a result, num is changed behind the scenes, which can surprise readers and make debugging more difficult.

Compliant Example: compl_ex_iPTgzrvO7qr3
status: draft
parent needs: gui_2jjWUoF1teOY

The same functionality, implemented as a function with explicit borrowing:

fn increment_and_double(x: &mut i32) -> i32 {
    *x += 1; // mutation is explicit
    *x * 2
}
let mut num = 5;
let result = increment_and_double(&mut num);
println!("Result: {}, Num: {}", result, num);
// Result: 12, Num: 6

The function version makes the mutation and borrowing explicit in its signature, improving readability, safety, and debuggability.

Guideline: Shall not use Function-like Macros gui_WJlWqgIxmE8P
status: draft
tags: reduce-human-error
category: mandatory
decidability: decidable
scope: system
release: todo

Description of the guideline goes here.

Rationale: rat_C8RRidiVzhRj
status: draft
parent needs: gui_WJlWqgIxmE8P

Explanation of why this guideline is important.

Non-Compliant Example: non_compl_ex_TjRiRkmBY6wG
status: draft
parent needs: gui_WJlWqgIxmE8P

Explanation of code example.

fn example_function() {
    // Non-compliant implementation
}
Compliant Example: compl_ex_AEKEOYhBWPMl
status: draft
parent needs: gui_WJlWqgIxmE8P

Explanation of code example.

fn example_function() {
    // Compliant implementation
}
Guideline: Shall not invoke macros gui_a1mHfjgKk4Xr
status: draft
tags: reduce-human-error
category: mandatory
decidability: decidable
scope: system
release: todo

Description of the guideline goes here.

Rationale: rat_62mSorNF05kD
status: draft
parent needs: gui_a1mHfjgKk4Xr

Explanation of why this guideline is important.

Non-Compliant Example: non_compl_ex_hP5KLhqQfDcd
status: draft
parent needs: gui_a1mHfjgKk4Xr

Explanation of code example.

fn example_function() {
    // Non-compliant implementation
}
Compliant Example: compl_ex_ti7GWHCOhUvT
status: draft
parent needs: gui_a1mHfjgKk4Xr

Explanation of code example.

fn example_function() {
    // Compliant implementation
}
Guideline: Shall not write code that expands macros gui_uuDOArzyO3Qw
status: draft
tags: reduce-human-error
category: mandatory
decidability: decidable
scope: system
release: todo

Description of the guideline goes here.

Rationale: rat_dNgSvC0SZ3JJ
status: draft
parent needs: gui_uuDOArzyO3Qw

Explanation of why this guideline is important.

Non-Compliant Example: non_compl_ex_g9j8shyGM2Rh
status: draft
parent needs: gui_uuDOArzyO3Qw

Explanation of code example.

fn example_function() {
    // Non-compliant implementation
}
Compliant Example: compl_ex_cFPg6y7upNdl
status: draft
parent needs: gui_uuDOArzyO3Qw

Explanation of code example.

fn example_function() {
    // Compliant implementation
}
Guideline: Shall ensure complete hygiene of macros gui_8hs33nyp0ipX
status: draft
tags: reduce-human-error
category: mandatory
decidability: decidable
scope: system
release: todo

Description of the guideline goes here.

Rationale: rat_e9iS187skbHH
status: draft
parent needs: gui_8hs33nyp0ipX

Explanation of why this guideline is important.

Non-Compliant Example: non_compl_ex_lRt4LBen6Lkc
status: draft
parent needs: gui_8hs33nyp0ipX

Explanation of code example.

fn example_function() {
    // Non-compliant implementation
}
Compliant Example: compl_ex_GLP05s9c1g8N
status: draft
parent needs: gui_8hs33nyp0ipX

Explanation of code example.

fn example_function() {
    // Compliant implementation
}
Guideline: Attribute macros shall not be used gui_13XWp3mb0g2P
status: draft
tags: reduce-human-error
category: required
decidability: decidable
scope: system
release: todo

Attribute macros shall neither be declared nor invoked. Prefer less powerful macros that only extend source code.

Rationale: rat_X8uCF5yx7Mpo
status: draft
parent needs: gui_13XWp3mb0g2P

Attribute macros are able to rewrite items entirely or in other unexpected ways which can cause confusion and introduce errors.

Non-Compliant Example: non_compl_ex_eW374waRPbeL
status: draft
parent needs: gui_13XWp3mb0g2P

Explanation of code example.

#[tokio::main]  // non-compliant
async fn main() {

}
Compliant Example: compl_ex_Mg8ePOgbGJeW
status: draft
parent needs: gui_13XWp3mb0g2P

Explanation of code example.

fn example_function() {
    // Compliant implementation
}
Guideline: Do not hide unsafe blocks within macro expansions gui_FRLaMIMb4t3S
status: draft
tags: reduce-human-error
category: required
decidability: todo
scope: todo
release: todo

Description of the guideline goes here.

Rationale: rat_WJubG7KuUDLW
status: draft
parent needs: gui_FRLaMIMb4t3S

Explanation of why this guideline is important.

Non-Compliant Example: non_compl_ex_AyFnP0lJLHxi
status: draft
parent needs: gui_FRLaMIMb4t3S

Explanation of code example.

fn example_function() {
    // Non-compliant implementation
}
Compliant Example: compl_ex_pO5gP1aj2v4F
status: draft
parent needs: gui_FRLaMIMb4t3S

Explanation of code example.

fn example_function() {
    // Compliant implementation
}
Guideline: Names in a macro definition shall use a fully qualified path gui_SJMrWDYZ0dN4
status: draft
tags: reduce-human-error
category: required
decidability: decidable
scope: module
release: 1.85.0;1.85.1

Each name inside of the definition of a macro shall either use a global path or path prefixed with $crate.

Rationale: rat_VRNXaxmW1l2s
status: draft
parent needs: gui_SJMrWDYZ0dN4

Using a path that refers to an entity relatively inside of a macro subjects it to path resolution results which may change depending on where the macro is used. The intended path to refer to an entity can be shadowed when using a macro leading to unexpected behaviors. This could lead to developer confusion about why a macro behaves differently in diffenent locations, or confusion about where entity in a macro will resolve to.

Non-Compliant Example: non_compl_ex_m2XR1ihTbCQS
status: draft
parent needs: gui_SJMrWDYZ0dN4

The following is a macro which shows referring to a vector entity using a non-global path. Depending on where the macro is used a different Vec could be used than is intended. If scope where this is used defines a struct Vec which is not preset at the macro defintion, the macro user might be intending to use that in the macro.

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new(); // non-global path
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
Compliant Example: compl_ex_xyaShvxL9JAM
status: draft
parent needs: gui_SJMrWDYZ0dN4

The following is a macro refers to Vec using a global path. Even if there is a different struct called Vec defined in the scope of the macro usage, this macro will unambigiously use the Vec from the Standard Library.

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = ::std::vec::Vec::new(); // global path
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}