macros
- 声明式宏( declarative macros )
- 过程宏( procedural macros )
常用宏
fn test_common_macro_declarative() {
println!("Hello, world!");
println!{"Hello, world!"};
println!["Hello, world!"];
let v = vec![1, 2, 3, 4, 5];
assert_eq!(1, 10);
panic!("Something went wrong!");
}
声明宏
声明宏(Declarative Macros),也称为“macro_rules!”,是最常见的宏类型。它们允许你通过模式匹配来生成代码。
fn main() {
say_hello!();
say_hello! {};
say_hello![];
}
// 定义一个简单的宏来打印消息
#[macro_export]
macro_rules! say_hello {
() => {
println!("Hello, world!");
};
}
内置申明宏
1. println!
- Purpose: Prints formatted text to the console, followed by a newline.
- Example:
println!("Hello, world!"); println!("The value of x is: {}", x);
2. print!
- Purpose: Prints formatted text to the console without a newline.
- Example:
print!("Hello, "); print!("world!");
3. format!
- Purpose: Formats text and returns it as a
String
. - Example:
let s = format!("Hello, {}!", "world");
4. eprintln!
- Purpose: Prints formatted text to the standard error (stderr), followed by a newline.
- Example:
eprintln!("An error occurred: {}", error_message);
5. vec!
- Purpose: Creates a
Vec
(vector) with the specified elements. - Example:
let v = vec![1, 2, 3, 4, 5];
6. panic!
- Purpose: Causes the program to terminate with an error message.
- Example:
panic!("Something went wrong!");
7. assert!
- Purpose: Asserts that a condition is true; if not, it causes a panic.
- Example:
assert!(x > 0);
8. assert_eq!
and assert_ne!
- Purpose: Asserts that two expressions are equal (
assert_eq!
) or not equal (assert_ne!
). - Example:
assert_eq!(x, 10); assert_ne!(x, y);
9. todo!
- Purpose: A placeholder that panics with a "not yet implemented" message.
- Example:
fn my_function() { todo!(); }
10. unimplemented!
- Purpose: Similar to
todo!
, indicates that a function is not yet implemented. - Example:
fn my_function() { unimplemented!(); }
11. dbg!
- Purpose: Prints the value of an expression along with the file name and line number, mainly for debugging purposes.
- Example:
let x = dbg!(2 + 2);
12. include!
, include_str!
, and include_bytes!
- Purpose: Includes the contents of a file as code (
include!
), a string (include_str!
), or bytes (include_bytes!
). - Example:
let config = include_str!("config.toml"); let data = include_bytes!("data.bin");
13. macro_rules!
Purpose: Defines a custom macro.
Example:
macro_rules! say_hello { () => { println!("Hello!"); }; } say_hello!();
14. matches!
- Purpose: Evaluates whether a value matches a given pattern.
- Example:
let x = Some(2); assert!(matches!(x, Some(2)));
15. concat!
- Purpose: Concatenates literals into a single string or byte string.
- Example:
let s = concat!("Hello, ", "world!");
16. env!
and option_env!
- Purpose: Accesses environment variables at compile time.
- Example:
let path = env!("PATH"); let maybe_home = option_env!("HOME");
17. cfg!
- Purpose: Checks at runtime whether a condition is met based on the configuration.
- Example:
if cfg!(target_os = "windows") { println!("Running on Windows!"); }
过程宏
过程宏(Procedural Macros)允许你使用函数生成代码。它们分为三种类型:
- 派生宏
- 属性宏 (Attribute-like macro)
- 函数宏 (Function-like macro)
- 派生宏:
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen.into()
}
[dependencies]
quote = "1.0.36"
syn = "2.0.74"
[lib]
proc-macro = true
In Rust, derive
macros are commonly used to automatically generate implementations for certain traits based on the structure of a type. Some of the most frequently used derive
macros include:
Debug
:- Automatically implements the
Debug
trait, which allows you to format the struct or enum for debugging purposes. This is particularly useful when you want to print a value usingprintln!("{:?}", value)
.
#[derive(Debug)] struct Point { x: i32, y: i32, }
- Automatically implements the
Clone
:- Implements the
Clone
trait, which allows for creating an exact copy of a value. This is particularly useful when you need a deep copy of a data structure.
#[derive(Clone)] struct Point { x: i32, y: i32, }
- Implements the
Copy
:- Implements the
Copy
trait, which is a marker trait indicating that a type’s values can be duplicated by simply copying bits (i.e., it doesn’t require deep copying). This is only applicable to types that can be trivially copied.
#[derive(Copy, Clone)] struct Point { x: i32, y: i32, }
- Implements the
PartialEq
andEq
:- Implement the
PartialEq
andEq
traits, which are used for equality comparisons.PartialEq
allows for comparison using==
and!=
, whileEq
indicates full equality (i.e., reflexive, symmetric, and transitive).
#[derive(PartialEq, Eq)] struct Point { x: i32, y: i32, }
- Implement the
PartialOrd
andOrd
:- Implement the
PartialOrd
andOrd
traits for ordering comparisons.PartialOrd
allows for comparison using<
,>
,<=
, and>=
, whileOrd
is used for total ordering.
#[derive(PartialOrd, Ord)] struct Point { x: i32, y: i32, }
- Implement the
Default
:- Implements the
Default
trait, which provides a default value for a type. This is useful for initializing a type with a sensible default value.
#[derive(Default)] struct Point { x: i32, y: i32, } let p = Point::default();
- Implements the
Hash
:- Implements the
Hash
trait, which allows the type to be used in hashed collections likeHashMap
orHashSet
. This trait is used to produce a hash value for an object.
#[derive(Hash)] struct Point { x: i32, y: i32, }
- Implements the
Serialize
andDeserialize
(from theserde
crate):- These are used for serializing and deserializing data structures to and from formats like JSON, XML, etc.
use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] struct Point { x: i32, y: i32, }
These derive
macros simplify code by reducing the need to manually implement these traits, especially when the implementation would be straightforward or repetitive.
- 函数宏:
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
In Rust, several function-like macros are commonly used to perform a variety of tasks, ranging from debugging to creating collections. Here are some of the most frequently used ones:
println!
- Usage: Prints formatted text to the console, followed by a newline.
- Example:
println!("Hello, world!"); println!("Number: {}", 10);
format!
- Usage: Constructs a formatted string.
- Example:
let formatted = format!("Hello, {}!", "world");
assert_eq!
- Usage: Asserts that two expressions are equal. It panics if the condition is not met, providing debugging information.
- Example:
assert_eq!(2 + 2, 4);
vec!
- Usage: Creates a
Vec
(dynamic array) with specified elements. - Example:
let numbers = vec![1, 2, 3];
- Usage: Creates a
panic!
- Usage: Causes the program to terminate and print a message. It's commonly used for unrecoverable errors.
- Example:
panic!("Something went wrong!");
include_str!
- Usage: Includes the contents of a UTF-8 encoded file as a string at compile time.
- Example:
let file_contents = include_str!("path/to/file.txt");
env!
- Usage: Fetches the value of an environment variable at compile time.
- Example:
let home_dir = env!("HOME");
concat!
- Usage: Concatenates literals into a single string at compile time.
- Example:
let full_path = concat!("/home/", "user", "/documents/");
cfg!
- Usage: Evaluates configuration options, typically used for conditional compilation.
- Example:
if cfg!(target_os = "windows") { println!("Running on Windows"); }
todo!
- Usage: Marks a part of the code that is yet to be implemented. It panics when executed.
- Example:
fn incomplete_function() { todo!("Implement this function"); }
These macros are integral to Rust programming, as they offer essential utilities for debugging, conditional compilation, and code generation. They help streamline code and provide functionalities that might otherwise require verbose or repetitive code.
- 属性宏:
#[route(GET, "/")]
fn index() {}
//
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
In Rust, attribute macros are used to modify the behavior of code at a higher level, typically by attaching metadata to items like functions, structs, modules, and more. Here are some common attribute macros used in Rust:
1. #[derive]
The derive
macro automatically generates implementations for common traits, such as Debug
, Clone
, Copy
, PartialEq
, Eq
, and Hash
.
Example:
#[derive(Debug, Clone, PartialEq)]
struct MyStruct {
name: String,
value: i32,
}
2. #[cfg]
and #[cfg_attr]
The cfg
attribute enables conditional compilation based on compile-time configuration options. The cfg_attr
attribute applies another attribute based on a condition.
Example:
#[cfg(target_os = "linux")]
fn linux_only_function() {
println!("This function only compiles on Linux.");
}
#[cfg_attr(feature = "special", derive(Debug))]
struct SpecialStruct {
value: i32,
}
3. #[test]
The test
attribute marks a function as a unit test that should be executed by cargo test
.
Example:
#[test]
fn test_addition() {
assert_eq!(2 + 2, 4);
}
4. #[allow]
, #[warn]
, and #[deny]
These attributes control how the compiler treats various types of warnings or errors.
Example:
#[allow(dead_code)]
fn unused_function() {
// This function won't cause a warning for being unused.
}
5. #[inline]
The inline
attribute suggests to the compiler that the function should be inlined to improve performance.
Example:
#[inline(always)]
fn fast_function(x: i32) -> i32 {
x * 2
}
6. #[no_mangle]
This attribute disables Rust's name mangling, making the function name accessible from other languages or tools.
Example:
#[no_mangle]
pub extern "C" fn my_function(x: i32) -> i32 {
x * 2
}
7. #[macro_use]
The macro_use
attribute allows macros defined in a module or crate to be available in the scope where the module or crate is imported.
Example:
#[macro_use]
extern crate my_macros;
8. #[repr]
The repr
attribute controls the memory layout of structs and enums.
Example:
#[repr(C)]
struct MyCStruct {
x: i32,
y: f64,
}
总结
- 创建声明宏:使用
macro_rules!
定义宏,通过模式匹配生成代码。 - 创建过程宏:使用过程宏函数(
#[proc_macro]
)、派生宏(#[proc_macro_derive]
)和属性宏(#[proc_macro_attribute]
)生成代码。
宏的应用场景
- 减少重复代码:通过宏生成重复的代码。
- 编译期计算:在编译期进行计算并生成代码,提高运行时性能。
- DSL(领域特定语言):使用宏定义领域特定语言,提高代码的表达力和可读性。
课后习题
通过macro_rules!
实现对应的 macro,并通过测试 case
assert_eq!(repeat!("x",3) ,"xxx");
assert_eq!(sum!(1,2,3,4,5), 15);
assert_eq!(max_value!(1,8,9), 9);
参考: