solana开发框架anchor入门工程tic_tac_toe源码解析(一,RUST宏)

use anchor_lang::prelude::*;

use num_derive::*;

use num_traits::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

此处使用declare_id宏定义了Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS,这个ID对所有anchor工程都是一样的。让我们来看一下他的原型:

#[proc_macro]

pub fn declare_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

    let id = parse_macro_input!(input as id::Id);

    proc_macro::TokenStream::from(quote! {#id})

}

#[macro_export]

macro_rules! quote {

    () => {

        $crate::__private::TokenStream::new()

    };

    ($($tt:tt)*) => {
   
   {

        let mut _s = $crate::__private::TokenStream::new();

        $crate::quote_each_token!(_s $($tt)*);

        _s

    }};

}

上面这几个宏定义就是declare!所有关联调用的宏了,那什么是宏?

简单地说,Rust宏让你可以发明自己的语法,编写出可以自行展开的代码, 也就是我们通常所说的元编程。Rust宏的基本运作机制就是:首先匹配宏规则中定义的模式,然后将匹配 结果绑定到变量,最后展开变量替换后的代码。
让我们看一下最简单的宏:

macro_rules! hellow_word {
    () => {}
}

如果你有C语言宏的知识理解起来并不困难。

左边的小括号部分是Rust宏的匹配器/Matcher,用来匹配模式并捕捉变量,右边的大括号部分是Rust宏的转码器/Transcriber,也就是我们把捕捉到的变量如何转化为实际代码的规则。那么什么是匹配模式?

($name:expr)其中name是将被使用在转码器中的变量,而expr是一个选择器,这部分声明为我们要匹配的类型。RUST宏具有多种选择器:

  • item:条目,例如函数、结构、模块等
  • block:代码块
  • stmt:语句
  • pat:模式
  • expr:表达式
  • ty:类型
  • ident:标识符
  • path:路径,例如 foo、 ::std::mem::replace, transmute::<_, int>, …
  • meta:元信息条目,例如 #[…]和 #![rust macro…] 属性
  • tt:词条树

当匹配器捕捉到变量后,转码器使用把变量直接嵌入到规则中。

($name:expr)=>{
    print!("hellow_world {}", $name)
}

这个宏打印了匹配中捕捉到的变量name。

让我们编写第一个完整的宏:

macro_rules! hellow_word {
    ($name:expr)=>{
    print!("hellow_world {}", $name)
    };
}

在上面这个宏里,我们只能接收一个变量,那如果需要多个变量呢?那么就需要用到重复模式的匹配。比如rust里经常用到的vec!就是使用重复模式匹配。

现在左边的匹配器会变成

($($name:expr), *)

此时多个参数就会被循环赋值到name中,你可以把这个想象成一个迭代器。

macro_rules! hellow_word {
    ($($name:expr),*)=>{
    print!("hellow_world {}", $name)
    };
}

目前这个宏匹配器一次只能接收一个变量即name,如果在一次迭代中我们想接收多个变量要如何做呢?

macro_rules! hellow_word {
    ($($name:expr=>$name2:expr),*)=>{
    print!("hellow_world {}", $name, &name2)
    };
}

此时我们的匹配器已经可以接收多次迭代并在单次迭代中接收多个变量,但是转码器却不能按我们所想接收多次迭代。那如何让转码器也像我们所想像的迭代器一样工作呢?

macro_rules! hellow_word {
    ($($name:expr=>$name2:expr),*)=>{
    $(print!("hellow_world {}", $name, &name2); )*
    };
}

在转码器中就会重复展开以接收匹配器重复传入的参数了。以上就是RUST宏中声明宏的基本使用方式。在RUST中还有另外一种宏,称之为程序宏。declare_id就是一个程序宏。而程序宏分为三种形式:

extern crate proc_macro; // 这是 Rust 提供的用来处理程序宏的
use proc_macro::TokenStream;
#[proc_macro]
pub fn declare_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let id = parse_macro_input!(input as id::Id);
    proc_macro::TokenStream::from(quote! {#id})
}

这个宏属于函数宏。函数宏的定义方式与函数很像。让我们接着往下:

#[program]

这也是一个宏,不过他是一个属性宏。我们看他的原型

#[proc_macro_attribute]
pub fn program(
    _args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    parse_macro_input!(input as anchor_syn::Program)
        .to_token_stream()
        .into()
}

在program宏里有两个入参_args, input

_args即为调用宏时的参数,而input即为紧跟着宏后的tokenStream,在这里是:

pub mod tic_tac_toe {
    use super::*;
    //初始化
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
    //建立游戏,此处传入2个参数,一个SetupGame, 一个是公钥,即地址,
    //此处由context上下文实现了对setupgame的包装,类型T即为SetupGame,accounts即为T对象
    pub fn setup_game(ctx: Context<SetupGame>, player_two: Pubkey) -> Result<()> {
        ctx.accounts.game.start([ctx.accounts.player_one.key(), player_two])
    }
    //玩家游戏,TILE是格子结构.
    pub fn play(ctx: Context<Play>, tile: Tile) -> Result<()> {
        let game = &mut ctx.accounts.game;    
        require_keys_eq!(
            game.current_player(),
            ctx.accounts.player.key(),
            TicTacToeError::NotPlayersTurn
        );  
    
        game.play(&tile)
    }
}

接着往下看:

#[derive(Accounts)]
pub struct Initialize {}

此处出现了程序宏的第三种:derive宏,让我们去看一下:

    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_builtin_macro]
    pub macro derive($item:item) {
        /* compiler built-in */
    }

那么derive宏到底做了什么?实际上derive宏为紧跟着他的struct或enum引入了特定的trait。在这段代码中引入的是Accounts这个trait。让我们看一下Accounts:

#[proc_macro_derive(Accounts, attributes(account, instruction))]
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
    parse_macro_input!(item as anchor_syn::AccountsStruct)
        .to_token_stream()
        .into()
}

此时Initialize这个struct就具有了Accounts这个trait,也意味上在这个struct可以调用这个trait的所有函数。

猜你喜欢

转载自blog.csdn.net/lixiaodog/article/details/126448491