有没有同学记得我们一起挖了多少个坑?嗯…其实我自己也不记得了,今天我们再来挖一个特殊的坑,这个坑可以说是挖到根源了——元编程

元编程是编程领域的一个重要概念,它允许程序将代码作为数据,在运行时对代码进行修改或替换。如果你熟悉Java,此时是不是想到了Java的反射机制?没错,它就是属于元编程的一种。

反射

Rust也同样支持反射,Rust的反射是由标准库中的std::any::Any包支持的。

这个包中提供了以下几个方法

TypeId是Rust中的一种类型,它被用来表示某个类型的唯一标识。type_id(&self)这个方法返回变量的TypeId。

is()方法则用来判断某个函数的类型。

可以看一下它的源码实现

pub fn is<T: Any>(&self) -> bool {
let t = TypeId::of::<T>(); let concrete = self.type_id(); t == concrete
}

可以看到它的实现非常简单,就是对比TypeId。

downcast_ref()downcast_mut()是一对用于将泛型T转换为具体类型的方法。其返回的类型是Option<&T>Option<&mut T>,也就是说downcast_ref()将类型T转换为不可变引用,而downcast_mut()将T转换为可变引用。

最后我们通过一个例子来看一下这几个函数的具体使用方法。

use std::any::{Any, TypeId};

fn main() {
let v1 = "Jackey";
let mut a: &Any;
a = &v1;
println!("{:?}", a.type_id());
assert!(a.is::<&str>()); print_any(&v1);
let v2: u32 = 33;
print_any(&v2);
} fn print_any(any: &Any) {
if let Some(v) = any.downcast_ref::<u32>() {
println!("u32 {:x}", v);
} else if let Some(v) = any.downcast_ref::<&str>() {
println!("str {:?}", v);
} else {
println!("else");
}
}

Rust的反射机制提供的功能比较有限,但是Rust还提供了宏来支持元编程。

到目前为止,宏对我们来说是一个既熟悉又陌生的概念,熟悉是因为我们一直在使用println!宏,陌生则是因为我们从没有详细介绍过它。

对于println!宏,我们直观上的使用感受是它和函数差不多。但两者之间还是有一定的区别的。

我们知道对于函数,它接收参数的个数是固定的,并且在函数定义时就已经固定了。而宏接收的参数个数则是不固定的。

这里我们说的宏都是类似函数的宏,此外,Rust还有一种宏是类似于属性的宏。它有点类似于Java中的注解,通常作为一种标记写在函数名上方。

#[route(GET, "/")]
fn index() {

route在这里是用来指定接口方法的,对于这个服务来讲,根路径的GET请求都被路由到这个index函数上。这样的宏是通过属于过程宏,它的定义使用了#[proc_macro_attribute]注解。而函数类似的过程宏在定义时使用的注解是#[proc_macro]

除了过程宏以外,宏的另一大分类叫做声明宏。声明宏是通过macro_rules!来声明定义的宏,它比过程宏的应用要更加广泛。我们曾经接触过的vec!就是声明宏的一种。它的定义如下:

#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}

下面我们来定义一个属于自己的宏。

自定义宏需要使用derive注解。(例子来自the book)

我们先来创建一个叫做hello_macro的lib库,只定义一个trait。

pub trait HelloMacro {
fn hello_macro();
}

接着再创建一个子目录hello_macro_derive,在hello_macro_derive/Cargo.toml文件中添加依赖

[lib]
proc-macro = true [dependencies]
syn = "0.14.4"
quote = "0.6.3"

然后就可以在hello_macro_derive/lib.rs文件中定义我们自定义宏的功能实现了。

extern crate proc_macro;

use crate::proc_macro::TokenStream;
use quote::quote;
use syn; #[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap(); // Build the trait implementation
impl_hello_macro(&ast)
} fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}

这里使用了两个crate:syn和quote,其中syn是把Rust代码转换成一种特殊的可操作的数据结构,而quote的作用则与它刚好相反。

可以看到,我们自定义宏使用的注解是#[proc_macro_derive(HelloMacro)],其中HelloMacro是宏的名称,在使用时,我们只需要使用注解#[derive(HelloMacro)]即可。

在使用时我们应该先引入这两个依赖

hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }

然后再来使用

use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro; #[derive(HelloMacro)]
struct Pancakes; fn main() {
Pancakes::hello_macro();
}

运行结果显示,我们能够成功在实现中捕获到结构体的名字。

总结

我们在本文中先后介绍了Rust的两种元编程:反射和宏。其中反射提供的功能能力较弱,但是宏提供的功能非常强大。我们所介绍的宏的相关知识其实只是皮毛,要想真正理解宏,还需要花更多的时间学习。

Rust入坑指南:万物初始的更多相关文章

  1. Rust入坑指南:鳞次栉比

    很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...

  2. Rust入坑指南:核心概念

    如果说前面的坑我们一直在用小铲子挖的话,那么今天的坑就是用挖掘机挖的. 今天要介绍的是Rust的一个核心概念:Ownership.全文将分为什么是Ownership以及Ownership的传递类型两部 ...

  3. Rust入坑指南:亡羊补牢

    如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大.它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃.所以今天我们就来聊一聊Ru ...

  4. Rust入坑指南:朝生暮死

    今天想和大家一起把我们之前挖的坑再刨深一些.在Java中,一个对象能存活多久全靠JVM来决定,程序员并不需要去关心对象的生命周期,但是在Rust中就大不相同,一个对象从生到死我们都需要掌握的很清楚. ...

  5. Rust入坑指南:齐头并进(上)

    我们知道,如今CPU的计算能力已经非常强大,其速度比内存要高出许多个数量级.为了充分利用CPU资源,多数编程语言都提供了并发编程的能力,Rust也不例外. 聊到并发,就离不开多进程和多线程这两个概念. ...

  6. Rust入坑指南:常规套路

    搭建好了开发环境之后,就算是正式跳进Rust的坑了,今天我就要开始继续向下挖了. 由于我们初来乍到 ,对Rust还不熟悉,所以我决定先走一遍常规套路. 变不变的变量 学习一门语言第一个要了解的当然就是 ...

  7. Rust入坑指南:坑主驾到

    欢迎大家和我一起入坑Rust,以后我就是坑主,我主要负责在前面挖坑,各位可以在上面看,有手痒的也可以和我一起挖.这个坑到底有多深?我也不知道,我是抱着有多深就挖多深的心态来的,下面我先跳了,各位请随意 ...

  8. Rust入坑指南:千人千构

    坑越来越深了,在坑里的同学让我看到你们的双手! 前面我们聊过了Rust最基本的几种数据类型.不知道你还记不记得,如果不记得可以先复习一下.上一个坑挖好以后,有同学私信我说坑太深了,下来的时候差点崴了脚 ...

  9. Rust入坑指南:有条不紊

    随着我们的坑越来越多,越来越大,我们必须要对各种坑进行管理了.Rust为我们提供了一套坑务管理系统,方便大家有条不紊的寻找.管理.填埋自己的各种坑. Rust提供给我们一些管理代码的特性: Packa ...

随机推荐

  1. 写react项目要注意的事项

    1,className一定是大写字母开头,例如:App-logo,App,App-header. 2,有关react元素的更新,唯一办法是创建新元素,然后重新将其传入ReactDOM.render() ...

  2. [每日一题系列] LeetCode 1013. 将数组分成和相等的三个部分

    题目: 给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false. 形式上,如果可以找出索引 i+1 < j 且满足 (A[0] + A[1] + . ...

  3. Python编程 从入门到实践-3列表下

    笔记出处(学习UP主视频记录) https://www.bilibili.com/video/av35698354?p=5 3.2.3 从列表中删除元素-使用del语句删除元素 motorcycles ...

  4. 一套代码同时支持.NET Framework和.NET Core

    转自:https://www.cnblogs.com/tianqing/p/11614303.html 在.NET Core的迁移过程中,我们将原有的.NET Framework代码迁移到.NET C ...

  5. Ubuntu 16 编译装python2.7

    Ubuntu 16自带 python3 ,有很多库无法使用,决定装 python2.7 试了试,好像 apt-get 装不上. 使用源码,编译安装. wget https://www.python.o ...

  6. Ajax上传数据和上传文件(三种方式)

    Ajax向后端发送数据可以有三种方式:原生Ajax方式,jQuery Ajax方式,iframe+form 方式(伪造Ajax方式) <!DOCTYPE html> <html la ...

  7. 初识JVM:(二)Java的垃圾回收机制详解

    声明:本文主要参考https://www.cnblogs.com/codeobj/p/12021041.html 仅供个人学习.研究之用,请勿用于商业用途,如涉及侵权,请及时反馈,立刻删除. 一.Ja ...

  8. 【Weiss】简单说一下这一分类下的东西

    主要是学习资料<数据结构与算法分析>(Weiss)的习题 除去习题外,每一章主要用到的数据结构先会写一个版本放上来,包括数据结构代码与测试用代码 这种先行上传的代码只具有基本的功能,毕竟一 ...

  9. 基于Ubuntu的ORB-SLAM2项目环境搭建过程

    目录 关于ORB-SLAM2 环境搭建 已有环境 创建环境 新建项目目录 安装Pangolin 安装OpenCV 3.2 安装Eigen DBoW2 and g2o (Included in Thir ...

  10. jvm 性能调优工具之 jstat 命令详解

    Jstat名称:Java Virtual Machine statistics monitoring tool 官方文档:https://docs.oracle.com/javase/1.5.0/do ...