Rust macros

이동창·2022년 6월 2일
0

매크로

Cosmwasm이든, Solana anchor이든, Substrate이든
블록체인 스마트 컨트랙트 관련 프레임워크들을 보면 많은 매크로를 지원하고 실제로 사용한다.
사실 그냥 모르고 써도 Docs보면 상관없긴 한데
그래도 얘가 뭐하는 애인지는 궁금하니까 좀 찾아보도록 하자

매크로 종류

  1. Function-like-macros
  2. Derive marcos
  3. Attribute macros

Function-like-macros

그냥 println! 같은거

Derive macros

derive attribute를 정의해주는 매크로
원래 crate안의 트레이트들은 정의만 하고, 구현은 crate의 user가 따로 해줘야하는데
Derive 매크로는 정해져 있는 구현까지 만들어주는 매크로라고 보면 된다.

예를 들어 HelloMacro라는 trait가 있고, 이 친구의 기본 구현은
Hello, Macro! My name is <TypeName>!를 출력해주는 것이라고 할 때

pub trait HelloMacro {
    fn hello_macro();
}

이것을 구현하려면 먼저 hello_macro라는 crate에 HelloMacro 라는 trait를 만들어주고,

use hello_macro::HelloMacro;

struct Pancakes;

impl HelloMacro for Pancakes {
    fn hello_macro() {
        println!("Hello, Macro! My name is Pancakes!");
    }
}

fn main() {
    Pancakes::hello_macro();
}

이를 사용하는 쪽에서 import 한 후에 위과 같이 impl을 통해 구현을 해줘야 쓸 수가 있다.
하지만 우리는 이 impl 없이도,
crate의 사용자가 기본 설정된 hello_macro를 사용할 수 있게끔 하는 것이 목표이다.

이를 위해서는 crate가 procedural macro라는 것을 Cargo.toml에 적어줘야한다.
또한 synquote는 매크로 만들 때 쓸거라 dependency에 적어준다.

[lib]
proc-macro = true

[dependencies]
syn = "1.0"
quote = "1.0"

그 후 lib.rs에 다음과 같이 선언하면 되는데

use proc_macro::TokenStream;
use quote::quote;
use syn;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
	// input으로 들어온 러스트 코드를 쉽게 조작할 수 있도록 syntax tree로 만들어준다.
    let ast = syn::parse(input).unwrap();

    // 우리가 곧 만들 함수, 여기에 위에서 만든 syntax tree를 집어 넣는다
    impl_hello_macro(&ast)
}

위에서 만든 ast는 대략 다음과 같은 구조를 갖고 있다

DeriveInput {
    // --snip--

    ident: Ident {
        ident: "Pancakes",
        span: #0 bytes(95..103)
    },
    data: Struct(
        DataStruct {
            struct_token: Struct,
            fields: Unit,
            semi_token: Some(
                Semi
            )
        }
    )
}

보다시피 ident에 Pancakes라는 타입이 적혀 있는 것을 볼 수 있다.
우리는 이를 출력해주면 되므로 impl_hello_macro를 다음과 같이 짤 수 있다.

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()
}

여기서 quote! 매크로가 무엇을 하는 친구인지는 공식문서로 확인하는 것으로 하고
간단하게 quote! 매크로는 syntax tree를 source code의 토큰들로 바꿔주는 친구라고 보면 된다.
다만, 컴파일러가 알아 들을 수 있는 형태, 즉 TokenStream로 만들어주려면 into 메소드를 이용해d야 한다.

그럼 다음과 같이 derive 매크로로 하나하나 impl 해야 하는 것을 대신 처리할 수 있다.

use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}

Attribute macros

Derive macros랑 비슷하지만, 코드를 만들어내는 것이 아닌 새로운 attribute를 생성하는 역할을 한다.
derivestructenum에만 먹히지만,
attribute는 조금 더 유연하게 다른 item, 함수와 같은 친구들에게도 적용될 수 있다.

잠깐! attribute가 뭐더라?
An attribute is a general, free-form metadatum that is interpreted according to name, convention, language, and compiler version. 참고자료

자세한 예제는 여기에 정말 잘 설명 되어 있다.

0개의 댓글

관련 채용 정보