Cosmwasm이든, Solana anchor이든, Substrate이든
블록체인 스마트 컨트랙트 관련 프레임워크들을 보면 많은 매크로를 지원하고 실제로 사용한다.
사실 그냥 모르고 써도 Docs보면 상관없긴 한데
그래도 얘가 뭐하는 애인지는 궁금하니까 좀 찾아보도록 하자
그냥 println!
같은거
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
에 적어줘야한다.
또한 syn
과 quote
는 매크로 만들 때 쓸거라 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();
}
Derive macros랑 비슷하지만, 코드를 만들어내는 것이 아닌 새로운 attribute를 생성하는 역할을 한다.
derive
는 struct
와 enum
에만 먹히지만,
attribute
는 조금 더 유연하게 다른 item, 함수와 같은 친구들에게도 적용될 수 있다.
잠깐! attribute가 뭐더라?
An attribute is a general, free-form metadatum that is interpreted according to name, convention, language, and compiler version. 참고자료
자세한 예제는 여기에 정말 잘 설명 되어 있다.