Skip to main content

28 posts tagged with "rust"

View All Tags

Rust Macros

· 4 min read
forfd8960
Author

What is a Rust Macro?

  • Core Concept: Macros are a way of writing code that writes other code, a form of metaprogramming.
  • Compile-Time Expansion: Unlike functions (which are called at runtime), macros are expanded during compilation. The Rust compiler replaces a macro invocation with the code generated by the macro before compiling the rest of the code.

Purpose

  • Reduce Boilerplate: Automate repetitive code patterns (DRY - Don't Repeat Yourself). The println!, vec!, and assert! macros are common examples.

  • Domain-Specific Languages (DSLs): Create syntax tailored for a specific problem domain within Rust code.

  • Variadic Functions: Implement functions that can take a variable number of arguments (like println!). Rust functions must have a fixed arity.

  • Extend Language Syntax: Introduce new syntactic constructs that aren't built directly into the core language.

How Many Types of Macros?

Rust fundamentally has two major types of macro systems:

  • a) Declarative Macros (macro_rules!)
  • b) Procedural Macros (which come in three kinds)

How to Apply Them Into Code (Details & Examples)

a) Declarative Macros (macro_rules!)

How They Work: Defined using the macro_rules! macro itself. They work by matching against patterns in the Rust code structure and replacing the matched code with the specified output template. They are similar in concept to match expressions but operate on code structure (token trees) instead of values.

macro_rules! macro_name {
// Rule 1: Match a specific pattern => Generate specific code
( $(pattern_matcher) ) => { $(code_template) };

// Rule 2: Match another pattern => Generate different code
( $(another_pattern) ) => { $(another_template) };

// ... more rules
}

Called using the macro name followed by ! and parentheses (), brackets [], or braces containing the arguments. Example: macro_name!(...), macro_name!{...}, macro_name![...].

Simple Example: A macro to create a simple HashMap

use std::collections::HashMap;

macro_rules! create_map {
// Match key-value pairs separated by commas
( $( $key:expr => $value:expr ),* $(,)? ) => {
{ // Use a block to create a scope
let mut map = HashMap::new();
$( // Repeat for each matched pair
map.insert($key, $value);
)*
map // Return the map
}
};
}

fn main() {
let my_map = create_map! {
"one" => 1,
"two" => 2,
"three" => 3
};
println!("{:?}", my_map); // Output: {"one": 1, "two": 2, "three": 3} (order may vary)
}

b) Procedural Macros (Proc Macros)

How They Work

These are more complex than declarative macros. They are essentially regular Rust functions that operate on the stream of tokens representing the input code and produce a new stream of tokens as output. They offer much more flexibility but are also harder to write

Requirement

Procedural macros must be defined in their own separate crate with a specific crate type declared in its Cargo.toml:

[lib]
proc-macro = true

Three Kinds of Procedural Macros

Custom #[derive] Macros

Allow you to automatically implement traits for structs and enums by adding #[derive(YourCustomTrait)] above the type definition.

A function marked with #[proc_macro_derive(TraitName)] that takes a TokenStream (representing the struct/enum definition) and returns a TokenStream (representing the generated impl block).

#[derive(YourDeriveMacroName)] above a struct or enum. Conceptual Example (Code is complex, showing usage):

// Assuming HelloDerive is defined in another proc-macro crate
use your_macros::HelloDerive;

#[derive(HelloDerive)] // Apply the custom derive
struct MyStruct {
field: i32,
}

fn main() {
// The derive macro might generate something like this:
// impl MyStruct {
// fn say_hello(&self) { println!("Hello from MyStruct!"); }
// }
let s = MyStruct { field: 10 };
// s.say_hello(); // This method would exist if generated
}

Attribute-like Macros

// Assuming route is defined in another proc-macro crate
use your_web_framework::route;

#[route(GET, "/users")] // Apply the attribute macro
fn list_users() {
// ... handler logic ...
}

// The macro might transform this into code that registers
// list_users function with a router for GET requests to "/users".

Function-like Macros

// Assuming sql is defined in another proc-macro crate
use your_db_macros::sql;

fn main() {
let user_id = 123;
// The macro could parse the SQL, check syntax at compile time,
// and generate code to execute it safely with parameters.
let user_name: String = sql!("SELECT name FROM users WHERE id = ?", user_id);
println!("User name: {}", user_name);
}

How Rust Trait Works Internally

· 4 min read
forfd8960
Author

Rust's traits are a powerful feature that enables polymorphism and code reuse. Internally, traits are implemented using a combination of static dispatch (monomorphization) and dynamic dispatch (vtable) mechanisms, depending on how they are used. Here's a detailed breakdown of how traits work internally:

Compare Rust and Golang Programming

· 11 min read
forfd8960
Author

In this blog, I will share the programming difference between Rust and Golang, Include:

  • How they use different syntax to declare data type.
  • How rust and golang define the common behavior(interface vs trait).
  • How rust and golang struct to implement the common behavior.
  • What's the difference between rust and golang about Error Handling.
  • How they define complex data type(struct).
  • How they define enum.
  • How they Obtaining and using third-party libraries.
  • What's the difference between rust and golang when write unit test.
  • What's the difference between rust and golang about async programming.

Rust Implement Tonic Stream Endpoint

· 5 min read
forfd8960
Author

Today want share how to implement a stream endpoint in Rust using the Tonic framework.

the dependency

[dependencies]
anyhow = "1.0.82"
chrono = { version = "0.4.38", features = ["serde"] }
derive_builder = "0.20.0"
futures = "0.3.30"
prost = "0.12.4"
prost-types = "0.12.4"
tokio = { version = "1.40.0", features = ["rt", "rt-multi-thread", "macros"] }
tokio-stream = "0.1.15"
tonic = { version = "0.11.0", features = ["zstd", "tls"] }
tonic-build = "0.11.0"
uuid = { version = "1.10.0", features=["v4"]}
fake = { version="2.9.2", features=["derive", "chrono"]}
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

[build-dependencies]
anyhow = "1.0.82"
tonic-build = "0.11.0"