您现在的位置是:首页 >学无止境 >Rust 宏网站首页学无止境
Rust 宏
前言
Rust 语言中的宏可以帮助你实现动态生成代码和简化代码的工作。以下是一个简单的示例,展示了如何在 Rust 中使用宏:
示例
// Define a macro
macro_rules! hello_world {
($x:expr) => {
println!("Hello, world! {}", $x);
};
}
fn main() {
// Call the macro with a variable
hello_world!("一碗情深");
}
在这个例子中,我们定义了一个名为 hello_world
的宏。这个宏接受一个参数 $x
.在 main
函数中,我们调用这个宏并传递了一个名为 “一碗情深” 的变量,并使用 println!
函数输出 “Hello, world! 一碗情深”。
需要注意的是,宏是由 macro_rules!
关键字定义的,宏的名称后面是它的形式(即 ($x:expr) => {...}
),接下来跟着要被替换的表达式,最后是冒号、花括号和表达式。在上面的例子中,宏 hello_world
将在后面的所有地方被替换为 println!("Hello, world! {}", $x);
。
简而言之,宏是一种机制,用于在编译时创建和执行代码,而不需要像其他语言(如 JavaScript,Python 等)那样在运行时编译代码。这在一些情况下非常有用,比如模板引擎、解析和生成代码等。
宏定义的语法允许在支持的标识符和操作符之间自由组合。除了像 expr
这样的标识符之外,你还可以将其他标识符(如 ident
、literal
、match
等)用于宏定义:例如,你可以在宏定义中使用 const
关键字:
macro_rules! hello_world_const {
($x:const String) => {
println!("Hello, world! {}", $x);
};
}
在这个例子中,$x:const String
是一个名为 String 的 Rust const 类型。const
关键字告诉我们,这个表达式的值在编译时是不可更改的。在宏调用 hello_world_const!(一碗情深)
中,“一碗情深” 的类型和值都保持不变。这使得我们可以像使用普通变量一样使用常量类型,而不必担心意外更改其值。
实战
新建 macros.rs
文件,写入以下代码:
#[allow(unused_macros)]
#[macro_export]
macro_rules! hashmap {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $val); )*
map
}}
}
#[allow(unused_macros)]
#[macro_export]
macro_rules! hashmap_s {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key.to_string(), $val); )*
map
}}
}
#[macro_export]
macro_rules! vec_s {
($( $val: expr ),*) => {{
let mut vec = ::std::vec::Vec::new();
$( vec.push($val.to_string()); )*
vec
}}
}
#[macro_export]
macro_rules! hashset {
($( $val: expr ),*) => {{
let mut vec = ::std::collections::HashSet::new();
$( vec.insert($val); )*
vec
}}
}
#[macro_export]
macro_rules! hashset_s {
($( $val: expr ),*) => {{
let mut vec = ::std::collections::HashSet::new();
$( vec.insert($val.to_string()); )*
vec
}}
}
在 main.rs
调用宏:
use std::collections::{HashSet, HashMap};
#[macro_use]
pub mod macros;
fn main() {
let my_vec: Vec<String> = vec_s!["点个赞", "关注我", "一碗情深"];
println!("my_vec: {:?}", my_vec);
let my_hashset: HashSet<&str> = hashset!["只能保存不同的值", "一样的值试试", "一样的值试试", "hashset宏数只能存入一种类型"];
println!("my_hashset: {:?}", my_hashset);
let my_hashset_num: HashSet<i32> = hashset![1,3,3,4];
println!("my_hashset_num: {:?}", my_hashset_num);
let my_hashset_s: HashSet<String> = hashset_s!["只能保存不同的值", "一样的值试试", "一样的值试试", "hashset_s宏数字会转成String", 2];
println!("my_hashset_s: {:?}", my_hashset_s);
let my_hashmap: HashMap<i32, (&str, bool)> = hashmap![
1 => ("一样的key会覆盖", true),
2 => ("文本2", false),
1 => ("一样的key会覆盖", false)
];
println!("my_hashmap: {:?}", my_hashmap);
}
运行结果:
my_vec: [“点个赞”, “关注我”, “一碗情深”]
my_hashset: {“只能保存不同的值”, “一样的值试试”, “hashset宏数只能存入一种类型”}
my_hashset_num: {3, 4, 1}
my_hashset_s: {“只能保存不同的值”, “一样的值试试”, “hashset_s宏数字会转成String”, “2”}
my_hashmap: {2: (“文本2”, false), 1: (“一样的key会覆盖”, false)}
在Rust编程语言中,#[allow(unused_macros)]
宏是一种编译器选项,它用来让你的Rust代码忽略特定的未使用的宏。
注意,Rust并未滥用编译时生命周期或者未使用的函数检查。这些特性的存在是为了帮助开发者写出安全的、不易出错的代码。当你使用未使用的宏时,这些特性可能无法正常工作,因为Rust无法知道这些宏何时被使用,或者它们的改动是否有可能在未来的代码中导致问题。
#[allow()]
宏用来告诉编译器,即使代码中出现违反规则的地方,也不要报错。在Rust中,描述未使用宏的规则很长,所有未被使用的宏都会被视为违反规则,这可能导致编译错误。当在一个 #[allow()]
标签内使用没有定义的宏时,这将不会引发错误。
#[allow(unused_macros)]
宏在你希望在代码中忽略某些全局的宏或者类型系统层面的宏时特别有用。因为在某些情况下,这些宏可能会因为可能的错误而导致代码不能正常执行。
在Rust语言中,#[macro_export]
是一个定义扩展宏的特殊标识。扩展宏是一种特殊类型的宏,它们可以在Rust标准库和第三方库中使用。它们适用于在代码中需要实现私有宏但又需要定义公共API的情况。
宏的主要目的是重用代码和实现可移值代码。我们可以在模块(例如 lib.rs)中定义私有宏,然后在其他模块(lib_a.rs,lib_b.rs等)中使用这些公共宏的实例。
你可以使用上述代码来定义一个只需要定义一次的类的公共API,而不需要为每个需要使用这个API的模块都定义一个新的函数。你的模块共享这些宏的定义,而不需要在每个模块中都包含这个公共宏的定义。
注:在Rust中,宏引入了很多抽象层,包括缩进(A和B宏都有相同的书写顺序,但A宏的作用会影响到B宏的作用)、marshalling和unmarshalling。因此,使用宏时需要特别小心,以避免出现预期之外的行为或错误。如果你不熟悉宏的工作原理,可能需要深入学习Rust的宏和其他高级特性,以避免编写出无法发现的错误。