前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >肝了三个视频:Rust 宏编程系列

肝了三个视频:Rust 宏编程系列

作者头像
tyrchen
发布2022-01-25 14:18:09
5520
发布2022-01-25 14:18:09
举报
文章被收录于专栏:程序人生

我的 B 站上正儿八经的和软件开发相关的视频,已经有半年没有更新了。最后一期《程序君的 Rust 培训 (2)》还是去年 6 月出品的,我记得肝那期时,正赶上西雅图百年一遇的酷暑,晚上十点多还有 39 度的高温,以至于我的 mbp 那几天经常会被热到关机自保。

之后,我业余时间基本上都在更新我在极客时间上的《Rust 第一课》。上个月终于结课,才慢慢有更多业余时间得以继续更新公众号,以及做些视频。做视频是很耗时耗力的一件事情,需要花费比写稿更多的时间和精力,所以我必然无法像更新公众号那样频繁。不过,和文字呈现最后的结果相比,视频天然适合把整个过程暴露给大家。而软件开发又是一个过程及其重要的活动,如果我把做一个项目的完整历程,包括经历的问题,做出的选择展现出来,相信对大家会有很多帮助。

如果说日子是问题叠着问题过的,那么代码就是选择叠着选择写的。我非常希望通过视频,不仅介绍知识本身,还能把我在 live coding 过程中做出的选择,无论是思路上的选择,设计上的选择,还是重构时的选择给表现出来,这样对我自己,对读者朋友们都更加有帮助。

这个长周末,Tubi Holiday 加马丁路德金日,一下子整出来四天假期,于是我有了大段的时间开始构思在《Rust 第一课》中,读者们呼声很高的宏编程,打算搞个加餐。写文字的时候,我突然想到,何不就要写的代码做个 live coding,录成视频,一鱼多吃?于是,就有了这个「Rust 过程宏」系列的三期视频。

第一期,我不用 syn/quote 徒手写了个通过 JsonSchema 生成 Rust struct 的函数宏,从最底层的逻辑出发,让大家了解 Rust 的 TokenStream,以及如何把包含代码的字符串转换成 TokenStream。感谢 Rust 的 FromStr trait,这个从字符串到 TokenStream 的动作简单到就是一个 s.parse().unwrap()

第二期,我使用 syn/quote 做了一个派生宏 Builder,可以为数据结构生成 builder 方法,让数据结构可以用非常简单直观的方法初始化。这个 Builder 宏的需求来自于 dtolnay 的 proc-macro-workshop 中的一个练习,Jon Gjengset 在他的 Procedural Macros in Rust 视频中,也使用它作为 proc macro 教学的素材。我大概一年前看的那个视频,它让我受益匪浅。不过,我不喜欢在宏处理的上下文中做所有的事情,而更加倾向于通过构建良好的数据结构,从 TokenStream 中获取我需要使用的数据,然后在自己的数据结构做进一步的处理,而非直接和TokenStream 或者 DeriveInput 打交道。

第三期,我为 Builder 宏添加了 attributes 的支持。由于 syn 的 DeriveInput 结构对 attribute 没有比较好的支持,你从语法树中拿到的就是 attributes 的 TokenStream,处理起来会非常繁琐。好在有 darling 这个第三方库,可以把 attributes 用数据结构捕获下来,就像 clap 3 / structopt 做的那样。

其实宏还有很多其他可讲的内容,我也在考虑哪些放在加餐中。比如 syn 的 Parse trait,就非常值得聊一聊 —— 我们通过 parse_macro_input!(input as DeriveInput) 之所以能把 TokenStream 转换成 DeriveInput,就是因为 DeriveInput 实现了 Parse trait。再比如,sqlx 里 query 宏,就为其内部数据结构 QueryMacroInput 也实现了它。所以才可以这么用:

代码语言:javascript
复制
#[proc_macro]
pub fn expand_query(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as query::QueryMacroInput);
    ...
}

我对宏的态度一直是这样的:宏编程并没有什么神秘的,它就跟我们平日里写的代码一样,只不过操作的数据结构是语法树,输出的数据结构也是语法树。在这个过程中,你要做的不过是从输入的语法树中抽取必要的元素,放入你自己的数据结构中,然后在通过你的数据结构生成新的语法树。所以,宏编程不过是一系列数据结构的转换而已。

这三个讲座,虽然我提供的例子非常简单,但已经涵盖了宏编程中你会遇到的主要情况。大家如果对宏编程感兴趣的话,可以在看完之后继续完成 proc-macro-workshop 里其它的例子。如果你耐心地把它们全做一遍,一定会有很大的收获。我希望通过这个系列,可以让你对宏编程不再畏惧。

不过凡事有两面。大家需要注意的是,宏编程是你撰写代码最后的手段。当一个功能可以用函数表达时,不要用宏。不要过分迷信于编译时的处理,不要把它当成提高性能的手段。虽然撰写宏并不困难,但宏会为别人理解你的代码,使用你的代码带来额外的负担。由于宏会生成代码,所以大量使用宏会让你的代码在不知不觉中膨胀,导致二进制很大。此外,正如我们看到的那样,目前 IDE 对宏的支持还不够好,这也是大量使用宏的一个问题。我们看到,像 nom 这样的工具,一开始大量使用宏,后来也都逐渐用函数取代。所以我们在开发的时候,要非常谨慎地构建宏。多问自己:我非用宏不可么?可以使用别的设计来避免使用宏么?就像同样是 web 框架,rocket 使用宏做路由,axum 完全不使用宏。这就是不同设计下带来的不同结果。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档