검색
이 검색 상자를 닫습니다.

Rust에서 절차적 매크로 작성하기

작성자 :

Qumulo의 소프트웨어는 한동안 완전히 C로 작성되었습니다. 최근 몇 년 동안 우리는 Rust와 그것이 제공해야 하는 많은 이점을 가지고 노닥거리고 있습니다. 이전에 이에 대해 썼습니다. 여기에서 지금 확인해 보세요..

최근에 우리는 자체 LDAP 클라이언트를 작성하는 작업을 하고 있으며 그 중 일부는 자체 ASN.1 BER 직렬화 라이브러리를 작성하고 있습니다. 우리는 종종 간과되는 Rust의 킬러 기능인 매크로 덕분에 직렬화를 생성하는 관용적 방법을 제공할 수 있었습니다.

매크로는 Rust가 등장하기 훨씬 전부터 많은 프로그래밍 언어의 요소였습니다. 그것들은 정상적인 컴파일 단계 이전에 평가되는 경향이 있으며 때로는 완전히 별도의 프로그램(전처리기라고 함)에 의해 평가되는 경향이 있습니다. 일반 컴파일 단계에서 컴파일할 더 많은 비 매크로 코드를 생성하는 데 사용됩니다. 여기 Qumulo에서 우리는 C의 매크로에 매우 익숙하지만 C 매크로는 Rust의 매크로와 매우 다릅니다. C 매크로는 텍스트 구문 분석 원칙에 따라 작동하는 반면 Rust 매크로는 토큰.

// C 매크로 #define LOCATION() (“file: ” __FILE__) // 매크로 macro_rules를 녹입니다! 위치 { () => { 연결!("파일: ", 파일!()) } }

사실 Rust에는 두 가지 다른 유형의 매크로가 있습니다. 하나는 C 매크로와 유사하게 정의되고 실행되며(위에 표시됨) 절차적 매크로. 이 매크로는 (매크로 언어 대신) Rust 자체로 작성되었습니다. 플러그인(동적 라이브러리)으로 컴파일되고 컴파일할 때 컴파일러에 의해 실행됩니다. 이것은 훨씬 더 복잡한 매크로를 쉽게 작성할 수 있음을 의미합니다. 그것들은 serde 또는 로켓과 같은 인기 있는 라이브러리를 지원하며 직렬화 라이브러리에서 사용한 종류의 매크로입니다. 그들은 몇 가지 유형으로 제공됩니다. 절차적 매크로 파생, 일반적으로 유형에 대한 특성을 자동으로 구현하는 데 사용됩니다.

특성 Hello { fn hello(); } // Foobar는 이제 Hello 특성을 구현하므로 // impl 블록을 작성할 필요가 없습니다! #[derive(Hello)] struct FooBar;

핵심에서 절차적 매크로는 토큰 스트림을 입력으로 사용하고 토큰 스트림을 출력으로 제공하는 기능일 뿐입니다. 그것들은 내가 구문 분석 단계와 생성 단계라고 부를 두 개의 별개의 단계를 갖는 경향이 있습니다.

토큰 스트림이 주어지기 때문에 토큰에 대한 구조적 정보를 얻으려면 먼저 토큰을 구문 분석해야 합니다. 이것은 다음을 사용하여 수행할 수 있습니다. 가 SYN. 구문 분석 단계에서 토큰은 syn 유형으로 구문 분석된 다음 매크로의 중간 유형으로 구문 분석됩니다.

생성 단계에서 구문 분석 단계의 유형을 활용하여 새 코드를 생성합니다. 그만큼 견적을 원하시면, 오늘 Kevin Lee Company 에 연락주세요. 라이브러리를 사용하여 일부 템플릿을 수행하고 토큰 스트림을 생성할 수 있습니다. 일반적으로 파생 절차 매크로를 사용하면 특성을 구현하는 코드를 출력합니다.

proc_macro::TokenStream을 사용하십시오. 사용 syn::{parse_macro_input, DeriveInput}; 인용문 사용:: 인용문; #[proc_macro_derive(Hello)] pub fn 파생_hello(input: TokenStream) -> TokenStream { // Parse Phase let 파생_입력 = parse_macro_input!(DeriveInput으로 입력); let ident = &derive_input.ident; 하자 이름 = 파생_입력.ident.to_string(); // 단계 생성(인용하기! { impl Hello for #ident { fn hello() { println!("Hello from {}", #name); } } }).into() }


파생 절차 매크로는 많은 작업을 절약할 수 있습니다. 많은 다른 유형에 대해 동일한 특성을 구현하는 대신 모든 유형에 대해 특성을 구현하는 매크로를 작성할 수 있습니다.

ber::{인코딩, 디코딩, DefaultIdentifier} 사용; #[derive(Encode, PartialEq, Debug, DefaultIdentifier, Decode)] struct StructPrimitives { x: u32, is_true: bool, negative: i32, } #[테스트] fn encode() { let s = StructPrimitives { x: 42, is_true : 참, 음수: -42 }; // 매크로 덕분에 인코딩 방법이 있습니다! mut 인코딩 = vec![]; s.encode(&mut 인코딩).unwrap(); }

Encode 구현을 명시적으로 작성하지 않았더라도 직렬화 라이브러리는 구조체 StructPrimitives를 인코딩할 수 있습니다. 절차적 매크로 덕분에 라이브러리는 자체적으로 작성할 수 있습니다.

절차적 매크로에 대한 컴파일러의 지원과 syn 및 quote와 같은 훌륭한 동반 라이브러리는 Rust에서 작성을 원활하게 만듭니다. 절차적 매크로는 우리가 Rust를 선택한 것을 기쁘게 생각하는 많은 이유 중 하나입니다. 우리는 이미 그것들을 다양한 용도로 활용했으며, 계속해서 새로운 용도를 찾을 것이라고 확신합니다.

또한! 캘린더를 표시하세요! Qumulo에서 우리와 함께 하십시오. 13월 6일 화요일 오후 30시 XNUMX분 위한 월간 시애틀 러스트 밋업. 음식과 음료가 제공됩니다!

Qumulo의 Collin Wallace는 다음과 같이 논의할 것입니다. “Qumulo는 메서드, 인터페이스 및 제한된 형태의 특성/제네릭을 허용하는 약간의 마법과 함께 대규모 C 코드베이스를 가지고 있었습니다. 이제 우리는 혼합된 C+Rust 코드베이스를 가지고 있으며 새로운 Rust 코드가 연결되는 C 코드와 어울리지 않는 느낌이 *없이* 관용적이어야 한다는 점을 선택했습니다. 이 강연에서 절차적 매크로, 컴파일러 플러그인, 자동 생성 C 헤더 및 몇 가지 사려 깊은 테스트 접근 방식을 활용하여 이를 달성하는 데 사용한 경로를 살펴보겠습니다.”

원하시면 여기로 연락주세요 회의 설정 또는 데모 요청. 과 블로그 구독 더 유용한 모범 사례 및 리소스를 위해!

관련 게시물

위쪽으로 스크롤