Rust入坑指南:萬物初始


有沒有同學記得我們一起挖了多少個坑?嗯…其實我自己也不記得了,今天我們再來挖一個特殊的坑,這個坑可以說是挖到根源了——元編程

元編程是編程領域的一個重要概念,它允許程序將代碼作為數據,在運行時對代碼進行修改或替換。如果你熟悉Java,此時是不是想到了Java的反射機制?沒錯,它就是屬於元編程的一種。

反射

Rust也同樣支持反射,Rust的反射是由標准庫中的std::any::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();
}

運行結果顯示,我們能夠成功在實現中捕獲到結構體的名字。

result

總結

我們在本文中先后介紹了Rust的兩種元編程:反射和宏。其中反射提供的功能能力較弱,但是宏提供的功能非常強大。我們所介紹的宏的相關知識其實只是皮毛,要想真正理解宏,還需要花更多的時間學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM