Skip to content

guideline: a macro should not be used in place of a function #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 17, 2025

Conversation

x0rw
Copy link
Contributor

@x0rw x0rw commented May 11, 2025

in markdown for easier reading and review:
(i'm unsure about the proper term for every italicized word below)

A macro should not be used in place of a function

:category: mandatory
:tags: reduce-human-error
:decidability: decidable

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:

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.(edit: outdated, misleading)
    Although the compiler reports both the macro expansion and its invocation site, diagnostics originating within macros can be more difficult to interpret than those from ordinary function or type definitions. Complex or deeply nested macros may obscure intent and hinder static analysis, increasing the risk of misinterpretation or overlooked errors during code review.

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

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

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.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Copy link
Collaborator

@PLeVasseur PLeVasseur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank @x0rw for adding this. Could you take a look at some of the comments?

Co-authored-by: Pete LeVasseur <[email protected]>
Copy link
Collaborator

@PLeVasseur PLeVasseur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @x0rw! Please take a look at the comments I left

x0rw and others added 2 commits May 16, 2025 17:41
Co-authored-by: Pete LeVasseur <[email protected]>
@x0rw x0rw force-pushed the guideline/macros-func branch from 37ed838 to 63e8d48 Compare May 16, 2025 16:56
Copy link
Collaborator

@PLeVasseur PLeVasseur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @x0rw -- this looks good!

@PLeVasseur PLeVasseur added this pull request to the merge queue May 17, 2025
Merged via the queue into rustfoundation:main with commit d41cb20 May 17, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants