Declarative macros in Rust

Declarative macros in Rust

Declarative macros in Rust, also known as "macro_rules!" macros, allow you to write code that writes other code, which can be very useful for reducing repetition and abstracting over patterns in your codebase. Here's an introduction to declarative macros in Rust:

Basic Structure

A declarative macro is defined using the macro_rules! keyword followed by the macro name and a set of rules. Each rule consists of a pattern and a corresponding expansion.

macro_rules! my_macro {

    (pattern) => {

        expansion

    };

}        

Simple Example

Let's start with a simple example of a macro that prints "Hello, world!".

macro_rules! hello {

    () => {

        println!("Hello, world!");

    };

}

fn main() {

    hello!();

}        

Matching Patterns

Macros can match various patterns and generate code accordingly. Here’s an example that takes an identifier and creates a function that prints that identifier.

macro_rules! create_function {

    ($func_name:ident) => {

        fn $func_name() {

            println!("You called {:?}", stringify!($func_name));

        }

    };

}

create_function!(foo);

create_function!(bar);

fn main() {

    foo();

    bar();

}        
macro_rules! calculate {
    (add $a:expr, $b:expr) => {
        println!("{} + {} = {}", $a, $b, $a + $b);
    };
    (sub $a:expr, $b:expr) => {
        println!("{} - {} = {}", $a, $b, $a - $b);
    };
}

fn main() {
    calculate!(add 3, 5);
    calculate!(sub 10, 4);
}
        


Repetition

Macros can also handle repetition, which is useful for generating code for multiple items. Here’s an example that generates multiple functions with a similar pattern.

macro_rules! create_functions {

    ($($func_name:ident),*) => {

        $(

            fn $func_name() {

                println!("You called {:?}", stringify!($func_name));

            }

        )*

    };

}

create_functions!(foo, bar, baz);

fn main() {

    foo();

    bar();

    baz();

}        

Using Macros with Expressions

Macros can accept expressions and manipulate them. Here’s an example that defines a macro to create a vector and initialize it with values.

macro_rules! create_vector {

    ($($x:expr),*) => {

        {

            let mut temp_vec = Vec::new();

            $(

                temp_vec.push($x);

            )*

            temp_vec

        }

    };

}

fn main() {

    let v = create_vector![1, 2, 3, 4, 5];

    println!("{:?}", v);

}        

Nested Macros

You can also nest macros. This is useful for complex code generation. Here's a basic example:

macro_rules! outer_macro {

    ($name:ident) => {

        macro_rules! $name {

            ($val:expr) => {

                println!("The value is: {}", $val);

            };

        }

    };

}

outer_macro!(inner_macro);

fn main() {

    inner_macro!(42);

}        

Conditional Compilation

Macros can include conditional compilation based on whether certain features are enabled.

macro_rules! conditional_macro {

    ($name:ident) => {

        #[cfg(feature = "my_feature")]

        fn $name() {

            println!("Feature enabled!");

        }

        #[cfg(not(feature = "my_feature"))]

        fn $name() {

            println!("Feature not enabled.");

        }

    };

}

conditional_macro!(my_func);

fn main() {

    my_func();

}        

Practical Example

A more practical example might be implementing a macro that defines getters and setters for struct fields.

[dependencies]
paste = "1.0"
        
#[macro_use]
extern crate paste;

macro_rules! define_struct {
    ($name:ident, $($field:ident: $type:ty),*) => {
        struct $name {
            $(
                $field: $type,
            )*
        }

        impl $name {
            $(
                fn $field(&self) -> &$type {
                    &self.$field
                }

                paste::paste! {
                    fn [<set_ $field>](&mut self, value: $type) {
                        self.$field = value;
                    }
                }
            )*
        }
    };
}

define_struct!(Person, name: String, age: u32);

fn main() {
    let mut p = Person {
        name: "Alice".to_string(),
        age: 30,
    };

    println!("Name: {}", p.name());
    println!("Age: {}", p.age());

    p.set_name("Bob".to_string());
    p.set_age(25);

    println!("Updated Name: {}", p.name());
    println!("Updated Age: {}", p.age());
}
        

In this example, the define_struct! macro creates a struct with the given fields and also generates getter and setter methods for each field.

Conclusion

Declarative macros are a powerful feature in Rust that can greatly reduce code duplication and make your code more expressive and flexible. While they can be complex to write and understand, they provide a significant advantage in many scenarios. Start with simple macros and gradually work towards more complex patterns as you become more comfortable with the syntax and capabilities.

To view or add a comment, sign in

More articles by Md. Mahadia Hossain

  • Rust Future Trait

    Rust Future Trait

    Implementing the trait in Rust and extracting the value from the future without using or external libraries can be a…

  • Downcasting in Rust

    Downcasting in Rust

    Rust is a systems programming language celebrated for its memory safety, concurrency, and performance. One of Rust’s…

  • Given keyword in Scala 3

    Given keyword in Scala 3

    In Scala 3, the concepts of given instances and using clauses (or context parameters) significantly enhance the…

  • Intersection and union types In Scala 3

    Intersection and union types In Scala 3

    In Scala 3, intersection and union types are powerful features that enhance type safety and flexibility in type…

  • Opaque types in Scala 3

    Opaque types in Scala 3

    Opaque types in Scala 3 provide a way to define type abstractions that are visible only within a specific scope…

  • Scala 3: inline keyword

    Scala 3: inline keyword

    In Scala 3, the inline keyword introduces powerful metaprogramming capabilities that allow for compile-time evaluation…

  • স্কালা বাইবেল

    স্কালা বাইবেল

    পৃথিবীতে যে কয়টা প্রগরামিং ভাষা আছে তাদের মধ্যে সবচেয়ে ভালোবাসার প্রগরামিং ভাষা হিসেবে বিবেচনা করা হয় Scala কে । এটা…

  • Actor ask pattern in Akka typed

    Actor ask pattern in Akka typed

    Yes, in akka typed the ask pattern is used in slightly different way. But how ? Actually, an akka ask pattern itself is…

  • Safe concurrency

    Safe concurrency

    কনকারেনসি একটি কষ্টকর প্রকৃয়া। যতটা সহজে এটা নিয়ে ভাবা যায়, এটা নিয়ে কাজ করা তত সহজ নয়। ফলে ভংঙ্কর সমস্যার…

  • S3 Browser-Based Uploads Using HTTP POST

    S3 Browser-Based Uploads Using HTTP POST

    To improve the application server performance we upload files to s3 using Browser-Based Http Post method. For allowing…

Insights from the community

Others also viewed

Explore topics