长春网站建设加王道下拉,购物型网站建设,在putty上怎样安装wordpress,怎么进入网络管理系统在 Rust 编程语言中#xff0c;宏是一种强大的工具#xff0c;可以用于在编译时生成代码。json! 是一个在 Rust 中广泛使用的宏#xff0c;它允许我们在 Rust 代码中方便地创建 JSON 数据。
声明宏#xff08;declarative macros#xff09;是 Rust 中的一种宏#xff0…在 Rust 编程语言中宏是一种强大的工具可以用于在编译时生成代码。json! 是一个在 Rust 中广泛使用的宏它允许我们在 Rust 代码中方便地创建 JSON 数据。
声明宏declarative macros是 Rust 中的一种宏它们使用 macro_rules! 关键字定义。
本文将参考《Rust 程序设计第二版》通过实现 json! 宏深入理解声明宏的工作原理。
结论先行
本文我们将构建一个 json! 宏它支持我们以字符串 JSON 风格的语法来编写 Json 值。如下面这个例子
let students json![{name: Hedon Wang,class_of: 2022,major: Software engineering},{name: Jun Lei,class_of: 1991,major: Computor science}
]完整代码 实现 json!
定义 Json enum
首先我们需要思考一下 Json 结构是什么样子的主要是以下 3 种模式
{name: hedon,age: 18,school: {name: Wuhan University,address: Hubwi Wuhan}
}[{name: hedon},{name: john}
]null为此我们定义一个 Json 结构的枚举
#[derive(Clone, PartialEq, Debug)]
pub enum Json {Null,Boolean(bool),Number(f64),String(String),Array(VecJson),Object(HashMapString, Json),
}你应该可以感到非常奇妙使用一个这么简单的枚举居然就可以表示所有的 Json 结构了。遗憾的是现在这个结构编写 Json 值的语法相当冗长。
let people Json::Object(HashMap::from([(name.to_string(), Json::String(hedon.to_string())),(age.to_string(), Json::Number(10.0)),(is_student.to_string(), Json::Boolean(true)),(detail.to_string(),Json::Object(HashMap::from([(address.to_string(), Json::String(beijing.to_string())),(phone.to_string(), Json::String(1234567890.to_string()))])))
]))我们期望可以以下面这种方式来声明 Json 变量这看起来就清爽许多了。
let students json!([{name: Jim Blandy,class_of: 1926,major: Tibetan throat singing},{name: Jason Orendorff,class_of: 1702,major: Knots}
]);猜想 json!
我们可以预见 Json 宏内部将会有多条规则因为 JSON 数据有多种类型对象、数组、数值等。事实上我们可以合理地猜测每种 JSON 类型都将有一条规则
macro_rules! json {(null) { Json::Null };([ ... ]) { Json::Array(...) };({ ... }) { Json::Object(...) };(???) { Json::Boolean(...) };(???) { Json::Number(...) };(???) { Json::String(...) };
}然而这不太正确因为宏模式无法区分最后 3 种情况稍后我们会讨论如何处理。至于前 3 种情况显然它们是以不同的语法标记开始的所以这几种情况比较好处理。
实现 Null
我们先从最简单的 Null 分支开始先编写如下测试用例
#[cfg(test)]
mod tests {use super::*;#[test]fn test_null_json() {let json json!(null);assert_eq!(json, Json::Null);}
}想要通过上述测试用例非常简单我们只需要在 macro_rules! 支持中匹配这种情况即可
#[macro_export]
macro_rules! json {(null) {Json::Null};
}#[macro_export] 注解是 Rust 中的一个属性用于指示这个宏应该被导出到调用者的作用域中这样其他模块也可以使用它。macro_rules! 宏定义了一个自定义的宏。在这里它创建了一个名为 json 的宏用于生成 JSON 数据。宏定义中 (null) 是匹配模式。这意味着当你调用 json! 宏并传递 null 作为参数时将会触发这个规则。 符号用于指示匹配模式后的代码块。在这里它指定了当匹配 (null) 时应该生成的代码块。Json::Null 是一个 JSON 类型的枚举值表示 JSON 中的 null 值。这个宏的目的是将传入的 null 转换为 Json::Null。
实现 Boolean/Number/String
我们先准备如下测试用例
#[test]
fn test_boolean_number_string_json() {let json json!(true);assert_eq!(json, Json::Boolean(true));let json json!(1.0);assert_eq!(json, Json::Number(1.0));let json json!(hello);assert_eq!(json, Json::String(hello.to_string()));
}通过观察分析它们其实都是同一种模式 现在需要解决的问题就是如何将这 3 种模式进行统一这样在 macro_rules! 中才可以统一匹配模式并进行代码生成。
这里我们其实需要做的就是将 bool、f64 和 str 转为对应的 Json 类型。那就需要用到标准库中的 From trait 了。
做法很简单我们实现如下代码
impl Frombool for Json {fn from(value: bool) - Self {Json::Boolean(value)}
}impl Fromstr for Json {fn from(value: str) - Self {Json::String(value.to_string())}
}impl Fromf64 for Json {fn from(value: f64) - Self {Json::Number(value)}
}然后完善我们的 json!目前的实现如下
#[macro_export]
macro_rules! json {(null) {Json::Null};($value: tt) {Json::from($value)};
}这里我们使用 $value作 为变量来承接匹配到的元素其类型为 tt 表示任意的语法标记树。具体可以参考片段类型。
这时运行上述测试用例是没有问题的 PASS [ 0.004s] json-macro tests::test_boolean_number_string_jsonPASS [ 0.004s] json-macro tests::test_null_json美中不足的是JSON 结构中的数字类型其实不一定是 f64也可以是 i32、u32、f32 或其他的数字类型如果我们要为这全部的数字类型都实现到 Json 的 From trait那就多冗余。
这个时候我们又可以实现一个宏用于快速生成 impl FromT for Json 。这个实现比较简单本文就不赘述了代码如下
#[macro_export]
macro_rules! impl_from_for_primitives {( $( $type: ty ) * ) {$(impl From$type for Json {fn from(value: $type) - Self {Json::Number(value as f64)}})*}
}然后我们只需要用下面这一行代码就可以为所有的数字类型实现 From trait 了
impl_from_for_primitives!(u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 isize usize);记得这个时候你要删除上面手动实现的 impl Fromf64 for Json不然会有 impl 冲突错误。
再次运行测试也是可以通过的。
实现 Array
准备如下测试用例
#[test]
fn test_array_json() {let json json!([1, null, string, true]);assert_eq!(json,Json::Array(vec![Json::Number(1.0),Json::Null,Json::String(string.to_string()),Json::Boolean(true)]))
}要匹配 [1, null, string, true]这个模式笔者的分析过程如下
首先是外面的两个中括号 [ 和 ] 再往里是一个重复匹配的模式以 , 分割可以匹配 0 到任意多个元素所以是 $( ,*) 具体可以参考重复模式最里面就是第 2 步要匹配的元素了我们先用 $element 作为变量来承接每一个元素其类型为 tt 表示任意的语法标记树。
分析完匹配的表达式后我们就可以得到
([ $( $element:tt ), * ]) { /* TODO */ }我们要生成的代码长这个样子
Json::Array(vec![Json::Number(1.0),Json::Null,Json::String(string.to_string()),Json::Boolean(true)
])其实就是一个 vec!然后里面每个元素都是一个 Json如此递归下去。
即可以得到代码生成部分的逻辑为
Json::Array(vec![$(json!($element)),* ])综上我们实现的代码如下
#[macro_export]
macro_rules! json {(null) {Json::Null};([ $( $element: tt),* ]) {Json::Array(vec![ $( json!($element)), * ])};($value: tt) {Json::from($value)};
}运行测试用例
PASS [ 0.003s] json-macro tests::test_null_json
PASS [ 0.003s] json-macro tests::test_boolean_number_string_json
PASS [ 0.004s] json-macro tests::test_array_json实现 Object
写好如下测试用例这次我们顺带把 Null、Boolean、Number 和 String 带上了
#[test]
fn test_object_json() {let json json!({null: null,name: hedon,age: 10,is_student: true,detail: {address: beijing,phone: 1234567890}});assert_eq!(json,Json::Object(HashMap::from([(name.to_string(), Json::String(hedon.to_string())),(age.to_string(), Json::Number(10.0)),(is_student.to_string(), Json::Boolean(true)),(detail.to_string(),Json::Object(HashMap::from([(address.to_string(), Json::String(beijing.to_string())),(phone.to_string(), Json::String(1234567890.to_string()))])))])))
}对比预期的 json! 宏内容和展开后的代码 完善我们的 macro_rules! json
#[macro_export]
macro_rules! json {(null) {Json::Null};([ $( $element: tt),* ]) {Json::Array(vec![ $( json!($element)), * ])};({ $( $key:tt : $value:tt ),* }) {Json::Object(HashMap::from([$(( $key.to_string(), json!($value) )), *]))};($value: tt) {Json::from($value)};
}运行测试用例
PASS [ 0.004s] json-macro tests::test_object_json
PASS [ 0.005s] json-macro tests::test_array_json
PASS [ 0.004s] json-macro tests::test_null_json
PASS [ 0.005s] json-macro tests::test_boolean_number_string_json至此我们就完成了 json! 宏的构建了完整源码可见完整代码
Peace! Enjoy coding~
附录
重复模式
在 实现 Array 中我们匹配了这样一个模式
([ $( $element:tt ), * ]) { /* TODO */ }其中 $($element:tt), *) 就是一个重复模式其可以进一步抽象为 $( ... ),* 表示匹配 0 次或多次以 , 分隔。
Rust 支持以下全部重复模式
模式含义$( … ) *匹配 0 次或多次没有分隔符$( … ), *匹配 0 次或多次以逗号分隔$( … ); *匹配 0 次或多次以分号分隔$( … ) 匹配 1 次或多次没有分隔符$( … ), 匹配 1 次或多次以逗号分隔$( … ); 匹配 1 次或多次以分号分隔$( … ) ?匹配 0 次或 1 次没有分隔符
即
* 表示 0 次或多次 表示 1 次或多次? 表示 0 次或 1 次可在上述 3 者之前加入分隔符
片段类型
在 实现 Array 中我们匹配了这样一个模式
([ $( $element:tt ), * ]) { /* TODO */ }这里我们将 $element 指定为 tt这个 tt 就是宏中的一种片段类型。
tt 能匹配单个语法标记树包含
一对括号如 (..)、[..]、或 {..} 以及位于其中的所有内容包括嵌套的语法标记树。单独的非括号语法标记比如 1926 或 Knots 。
所以为了匹配任意类型的 Json 我们选择了 tt 作为 $element 的片段类型。
macro_rules! 支持的片段类型如下所示
片段类型匹配带例子后面可以跟 ······expr表达式2 2, “udon”, x.len(),;stmt表达式或声明不包括任何尾随分号很难用请尝试使用 expr 或 block,;ty类型String, Vec, (str, bool), dyn Read Send,; path路径ferns, ::std::sync::mpsc,; pat模式_, Some(ref x),item语法项struct Point { x: f64, y: f64 }, mod ferns;任意block块{ s “ok\n”; true }任意meta属性的主体inline, derive(Copy, Clone), doc“3D models.”任意literal字面量值1024, “Hello, world!”, 1_000_000f64任意lifetime生命周期a, item, static任意vis可见性说明符pub, pub(crate), pub(in module::submodule)任意ident标识符std, Json, longish_variable_name任意tt语法标记树;, , {}, [0 1 ( 0 1)]任意
完整代码
use std::collections::HashMap;#[derive(Debug, Clone, PartialEq)]
#[allow(unused)]
enum Json {Null,Boolean(bool),String(String),Number(f64),Array(VecJson),Object(HashMapString, Json),
}impl Frombool for Json {fn from(value: bool) - Self {Json::Boolean(value)}
}impl Fromstr for Json {fn from(value: str) - Self {Json::String(value.to_string())}
}impl FromString for Json {fn from(value: String) - Self {Json::String(value)}
}#[macro_export]
macro_rules! impl_from_for_primitives {( $( $type: ty ) * ) {$(impl From$type for Json {fn from(value: $type) - Self {Json::Number(value as f64)}})*}
}impl_from_for_primitives!(u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 isize usize);#[macro_export]
macro_rules! json {(null) {Json::Null};([ $( $element: tt),* ]) {Json::Array(vec![ $( json!($element)), * ])};({ $( $key:tt : $value:tt ),* }) {Json::Object(HashMap::from([$(( $key.to_string(), json!($value) )), *]))};($value: tt) {Json::from($value)};
}#[cfg(test)]
mod tests {use super::*;#[test]fn test_null_json() {let json json!(null);assert_eq!(json, Json::Null);}#[test]fn test_boolean_number_string_json() {let json json!(true);assert_eq!(json, Json::Boolean(true));let json json!(1.0);assert_eq!(json, Json::Number(1.0));let json json!(hello);assert_eq!(json, Json::String(hello.to_string()));}#[test]fn test_object_json() {let json json!({null: null,name: hedon,age: 10,is_student: true,detail: {address: beijing,phone: 1234567890}});assert_eq!(json,Json::Object(HashMap::from([(null.to_string(), Json::Null),(name.to_string(), Json::String(hedon.to_string())),(age.to_string(), Json::Number(10.0)),(is_student.to_string(), Json::Boolean(true)),(detail.to_string(),Json::Object(HashMap::from([(address.to_string(), Json::String(beijing.to_string())),(phone.to_string(), Json::String(1234567890.to_string()))])))])))}#[test]fn test_array_json() {let json json!([1, null, string, true]);assert_eq!(json,Json::Array(vec![Json::Number(1.0),Json::Null,Json::String(string.to_string()),Json::Boolean(true)]))}
}