rust開發(fā)web項(xiàng)目附錄一——簡(jiǎn)單的proc macro教程
鑒于我們需要在irmin上層編寫數(shù)據(jù)管理相關(guān)的功能,而此類功能為了方便用戶也為了高效性,自是繞不開proc macro,由于在下自己對(duì)proc macro亦是使用多過編寫,這里便從頭開始編寫一篇proc macro的教程,本文主要參考的資料均為社區(qū)久負(fù)盛名的文檔,如果各位觀看完之后仍深感疑惑,那說明在下的水平有很大問題,與引用的資料無關(guān)(疊甲,過)
官方文檔對(duì)proc macro的解釋是,讓你可以在編譯期執(zhí)行操作rust語法的代碼,既可以消耗掉rust代碼,也可以生成rust代碼,可以理解為一個(gè)作用于AST(抽象語法樹)的函數(shù)
從以上描述可以大概猜測(cè)proc macro在編譯的哪一步執(zhí)行(不知道的自行補(bǔ)充一下編譯原理的知識(shí)),整體來說可以簡(jiǎn)單的理解為在編譯期對(duì)代碼生成的抽象語法樹進(jìn)行修改
可能很抽象,但是沒關(guān)系,我們一點(diǎn)一點(diǎn)來了解,首先來創(chuàng)建一個(gè)用于學(xué)習(xí)proc macro的項(xiàng)目,注意要開啟proc macro
然后,最基本的proc macro可以是一個(gè)簡(jiǎn)單的編譯期函數(shù)(注意,此場(chǎng)景下不如直接編寫另一種宏來的方便快捷)
proc macro 不能在創(chuàng)建的庫使用,故而本文的測(cè)試將有單獨(dú)的一個(gè)項(xiàng)目來負(fù)責(zé),這里為了閱讀體驗(yàn)就不涉及對(duì)應(yīng)項(xiàng)目的創(chuàng)建了
測(cè)試一下:

那么,實(shí)際上這里的宏寫起來有點(diǎn)像D語言的mixin,但顯然如果只是用字符串的話,那可算不上方便,但我們現(xiàn)在還是新手,得先了解proc macro本身,之后再去進(jìn)階
那么,接下來了解重點(diǎn),derive macro和attribute?macro,首先還是一樣的是接收Token Stream,返回TokenStream的函數(shù)
注意,這里我們只是使用derive macro創(chuàng)建了一個(gè)函數(shù),并沒有干別的事情,而且該函數(shù)并不會(huì)成為這個(gè)struct的成員或怎么樣,只是單純的添加了一個(gè)函數(shù)
那么,將測(cè)試修改為:
依舊會(huì)得到hello world(這里rust analyser可能會(huì)報(bào)錯(cuò),但實(shí)際上并無錯(cuò)誤)
然后是作為derive macro的一部分的attribute macro,大家多少都用過,我也就不多說了,這里直接寫一下例子
這里定義了一個(gè)無意義的attribute宏,該類型的宏的主要作用是用來做標(biāo)記
由于該宏沒有任何意義,這里就不追加測(cè)試了(浪費(fèi)鍵盤耐久度)
另一種attribute macro則有所不同,該macro接收兩個(gè)參數(shù)一個(gè)是括號(hào)內(nèi)的參數(shù),一個(gè)是被derive的對(duì)象,該macro與derive目的一致,但效果略有不同
到這里為止,rust reference便無更多有意義的參考了,例如TokenStream是個(gè)怎樣的類型(作為一個(gè)容器類型,表明上看不出具體的存儲(chǔ)方式和可能的值),我們?nèi)绾胃奖愕奶峁┳鳛榉祷刂档腡okenStream,此時(shí)就需要參考一些其他的文檔
而rust book則稍微提供了一些,之所以將這個(gè)置于rust reference之后,主要原因是該內(nèi)容沒有對(duì)derive macro做基本的介紹和解釋,便直接開始引用syn和quote庫進(jìn)行編寫,雖然也能行得通,但未免太過跳躍
那么,我們便引入syn和quote

那么,接下來,我們假設(shè)我們有一個(gè)trait需要通過derive宏來提供默認(rèn)的基于struct本身成員的實(shí)現(xiàn),但開始之前我們得先對(duì)這兩個(gè)庫有點(diǎn)基本的了解
那么,老規(guī)矩,cargo doc?--open
然后我們可以看到我們此時(shí)依賴的crate之中甚至還有個(gè)proc-macro2(其實(shí)和proc-macro并非版本更新的關(guān)系)
那么從這個(gè)庫的文檔開始看起,首先該庫是上述我們提到的兩個(gè)庫中syn的依賴項(xiàng),該庫在官方標(biāo)準(zhǔn)的proc-macro的上層,但支持從proc-macro的TokenStream到其自己的TokenStream之間相互轉(zhuǎn)換,倒也不算麻煩,這里我們主要了解一下這個(gè)庫提供的一些類型,例如,Ident之類的,這些對(duì)我們后續(xù)解析宏和上下文中的其他東西大有幫助

然后我來看quote,quote這個(gè)庫提供了一些工具用于將rust語法轉(zhuǎn)換為TokenStream,使得用戶在編寫derive宏的時(shí)候可以使用類似macro_rule的方式,以下是該庫提供的宏和traits:

最后是syn,之所以將其放在最后,是該庫依賴于上述兩個(gè)庫,加之其本身也比另外兩個(gè)庫復(fù)雜許多
但該庫使用起來卻并不復(fù)雜,由于其本身是TokenStream到語法樹的轉(zhuǎn)換庫,故而其上層api主要為此服務(wù)
這里我們并不需要深入學(xué)習(xí)該庫,只需要其提供的解析derive input的功能即可
首先一個(gè)常規(guī)的proc-macro也許長(zhǎng)這樣:
這里用到了DeriveInput類型,我們簡(jiǎn)單看一下這個(gè)類型定義:

此時(shí)我們可以把之前的hello_world macro修改成下面這樣:
此時(shí),該宏將為對(duì)應(yīng)類型實(shí)現(xiàn)一個(gè)hello world trait(注意,由于proc-macro開啟的庫是無法聲明公開的trait的,所以,我們需要定義在別的的地方,這里的實(shí)現(xiàn)部分在當(dāng)前庫沒有定義trait的情況下依然可用,你自己清楚你的trait的標(biāo)準(zhǔn)即可),然后測(cè)試一下:

那么其他的macro形式也類似,再牛逼的庫的derive功能也都是這些簡(jiǎn)單的宏構(gòu)成的,各位多練習(xí)應(yīng)當(dāng)就能達(dá)成自己的使用要求了
rust reference的proc macro相關(guān)文檔:https://doc.rust-lang.org/reference/procedural-macros.html#procedural-macros
rust official book的相關(guān)文檔:https://doc.rust-lang.org/book/ch19-06-macros.html
最后推薦還想進(jìn)一步了解的各位可以去看一下大佬的庫,例如serde