Sling Academy
Home/Rust/Rust macros cheat sheet

Rust macros cheat sheet

Last updated: January 02, 2025

Macros in Rust provide a way to write code that writes code, enabling developers to eliminate redundancy, increase code clarity, and reduce bugs in repetitive tasks. Rust's macro system is quite powerful but can be difficult to grasp for newcomers. In this cheat sheet, we will demystify macros in Rust with easy-to-follow explanations and examples.

What Are Rust Macros?

Macros in Rust are a way to define rules that are expanded at compile time. Unlike functions, macros work with tokens, allowing developers to perform meta-programming by generating code based on patterns. There are mainly two types of macros in Rust: declarative macros (macros by example) and procedural macros.

1. Declarative Macros (Macros by Example)

These are defined using the macro_rules! construct and allow defining macros that match against patterns. For instance, the classic 'println!' macro is a declarative macro.

macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

Usage:

fn main() {
    say_hello!();
}

In this example, the macro 'say_hello' matches an empty token (a pair of parentheses) and expands to the code that prints "Hello!" on the screen.

2. Procedural Macros

Procedural macros operate at a more granular level, suitable for scenarios where more complex processing of Rust code is required, such as deriving traits or modifying the syntax tree itself. They come in three types: Custom Derive, Attribute-like, and Function-like macros.

Custom Derive

Custom Derive macros are used when you want to automatically implement a trait for a struct or enum.

// Define a procedural macro library
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // parse input, generate tokens
}

Attribute-like Macros

These macros look similar to annotations in other languages and are a powerful way to modify items like functions, structs, etc.

#[my_attribute_macro]
fn my_function() {
    println!("I'm a function with an attribute macro!");
}

Function-like Macros

Function-like macros resemble regular function calls and are similar to declarative macros but allow for more complex transformations.

my_macro!(arg1, arg2);

Advantages of Using Macros in Rust

Macros increase code conciseness by abstracting repetitive patterns. They can enforce additional compile-time checks ensuring safety and correctness, and decrease the risk of copy-paste errors by managing code generation consistently.

Caveats and Best Practices

  • Macros should be documented and used judently since extensive use can lead to harder-to-read code.
  • The more cryptic or logic-heavy a macro is, the more it should be reviewed for potential compilation errors.
  • Lean towards using clearer declarative macros before considering procedural macros.

Conclusion

Rust macros offer a true metaprogramming toolkit that can streamline many of the routine activities you're bound to encounter. With the power to generate complex code at compile time, knowing when and how to use them will reward you with cleaner, safer, and more effective Rust programs. Keep this cheat sheet handy and practice by writing simple macros, then gradually tackle more complex scenarios as you get comfortable with Rust's macro capabilities.

Next Article: Preserved keywords in Rust: A cheat sheet

Previous Article: What are Rust macros?

Series: The basics of Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior