ゼロから Rust の手続き型マクロ (Procedural Macro) を構築する

2026/06/01 4:28

ゼロから Rust の手続き型マクロ (Procedural Macro) を構築する

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

この章では、struct に対して bit フラグ用メソッドを自動的に生成する Rust プロシージャルマクロ

bitfields
の実装方法を説明します。マクロは実行時変数ではなくソースコード上でコンパイル時に動作し、
TokenStream
入力を
TokenStream
出力にマッピングする関数として定義されます。これらは
Cargo.toml
proc-macro = true
とマークされた独立したクレート内に配置する必要があります。Rust ではプロシージャルマクロとして
#[proc_macro]
#[proc_macro_derive]
、および
#[proc_macro_attribute]
の 3 種類が用意されています。ソースコードを操作するには、入力文法解析器(AST, Abstract Syntax Tree)に変換する
syn
クレートと、その AST から新しい Rust トークンを生成する
quote!
マクロを使用します。属性は
Meta::List
Meta::NameValue
などの構造体として解析されます。マクロは通常の関数では実現できないコンパイル時の挙動を実行できます(例:
break
文の挿入)。文法解析にはカスタムキーワード(例:
dont_shift
)を
syn::custom_keyword!
を用いて、または「フォーク」などのエラー回復技術を用いて行うことができます。フラグ用のビットマスク生成は、
(1 << n) - 1
u8::MAX >> (u8::BITS - n)
のようなビット演算に依存します。生成されたマクロは struct のフィールドに基づいて、ゲッター(
get_*
/
is_*
)、セッター(
set_*
)、クリア関数、コンストラクタメソッドを提供します。マクロ展開時のエラー処理では
compile_error!
を用いて実行時パニックを回避することが推奨されます。このアプローチは論理を実行時変数からコンパイル時コード操作へシフトさせ、フラグ管理のための boilerplate を削減するとともに、プロジェクト間で標準化された一貫した安全性チェックを実現します。

本文

LearnixOS 書籍:Bitflags マクロの実装ガイド

キーボードショートカット

キー動作
/ 章間の移動
S / /書籍の検索
?ヘルプ表示
Escヘルプを隠す

Bitflags マクロの導入

プロシージャルマクロとは?

  • 定義: 「入力から出力へどのように置換するか」を指定する規則またはパターン。
    • 関数同様、入力を出力にマップしますが、ソースコードそのものに作用します。
  • Rust の proc-macro 特徴:
    • 初期のコード(
      TokenStream
      )を操作可能にする。
    • ソースコードをトークン化し、
      TokenTree
      ノードのシーケンスとして扱う。

基本的な実装例

#[proc_macro]
pub fn custom_proc_macro(input: TokenStream) -> TokenStream {
    eprintln!("{:?}", input);
    input // 同じものを返す(多くの場合は異なるコードを生成する)
}

トークンストリーム (
TokenStream
) とは?

  • 文字列ではなくトークン単位で処理することで、初期のコードレベルでの操作が可能。
  • TokenTree
    ノードのシーケンスを含みます。
    • Group
      : ブレースなどのデリミター付きストリーム
    • Ident
      : 識別子(例:変数名)
    • Punct
      : 句読点文字(例:
      +
      ,
      ,
    • Literal
      : リテラル文字、文字列、数値

デバッグ出力の例

TokenStream [
    Ident { ident: "struct", ... },
    Ident { ident: "Example", ... },
    Group {
        delimiter: Brace,
        stream: TokenStream [
            Ident { ident: "a", ... },
            Punct { ch: ':', ... },
            Ident { ident: "i32", ... },
            Comma,
        ],
    },
]

マクロの動作原理(展開)

  • コンパイル時に評価される関数。
  • コンパイラ視点:ターゲット言語を別の言語へマップするのではなく、同じ言語内で置換

例:宣言的マクロの展開

// 定義
macro_rules! square {
    ($num:expr) => { $num * $num };
}

// 使用
fn foo() -> u32 {
    let x: u32 = 42;
    square!(x) // 実質的に:x * x
}

関数ではできないこと:コンテキスト依存の制御流

  • 宣言的マクロが実現できる抽象化(例:ループからの抜出)。
macro_rules! unwrap_or_break {
    ($e:expr) => {
        match $e {
            Some(v) => v,
            None => break, // コンパイル時に入れ子された文脈 (`break`) を理解可能
        }
    };
}

// 関数実装だとエラーになる:None -> break は外部のスコープ参照が必要
fn unwrap_or_break<T>(e: Option<T>) -> T {
    match e {
        Some(v) => v,
        None => panic!("Error"), // ここでは break を使用できない
    }
}

マクロの種類

種類デコレータ特徴対象
宣言的マクロなし規則セットで展開、文法拡張に用いる全スコープ
関数型マクロ
#[proc_macro]
TokenStream
を受け取って返す(通常の関数同様)
グローバルスコープ
誘導マクロ
#[proc_macro_derive]
項目に対して自動生成コード (
derive
)。トレイト実装などに使用。
構造体、列挙型など
属性マクロ
#[proc_macro_attribute]
項目の動作をカスタマイズ(設定パラメータを受け取る)構造体、関数など

Syn と Quote の導入

背景と必要性

  • プロシージャルマクロでは直接
    TokenStream
    を解析するのは複雑。
  • Syn: Rust の構文ツリーを**AST(抽象構文木)**に変換する。
  • Quote: データ (
    TokenStream
    ) をコードのように見える形式に変換する。

AST とは?

  • プログラムの構文構造を表す木構造。
  • 操作しやすいデータ構造にするために、簡略化された Python の例:
current = 0
for item in items:
    if item > current:
        current = item

Syn で利用可能な主要 AST タイプ

  • syn::File
    : 完全な Rust ソースファイル。
  • syn::Attribute
    : 属性(例:
    #[derive]
    )。
  • syn::Item
    : モジュール内にある項目(定数、列挙体、関数、構造体など)。
  • syn::Expr
    : 式(割り当て文、スライスリテラルなど)。

重要: AST のフィールド順序はソースコードの順序と一致する。これにより解析後の再構築が容易になる。

Quote と ToTokens

  • Quoting: コードのように見せるが裏ではデータ (
    TokenStream
    ) として扱われる。
  • ToTraits:
    quote!
    で使用可能なためのトレイト。
// クオートされた式(実際には TokenStream に変換される)
quote! { struct Foo { bar: () } }

// 変数をトークンに変換する例
let mut item_fn = syn::parse_macro_input!(input as syn::ItemFn);
item_fn.sig.ident = Ident::new("with_change_{}", ...);

quote! { #item_fn }.into() // `#` で変数を挿入

マクロの設計:Bitfields

要件と仕様

数値をフラグとして表現する(例:

u8
,
u16
)。各フラグに対し、以下を実装。

  • Getter: フラグ値の取得 (
    get_*
    )。
  • Setter: フラグ値の設定 (
    set_*, new()
    )。
  • Clearer: フラグのリセット (`clear_*)。

機能拡張機能(Attribute)

生成される関数を制御するメタデータ:

  1. Permission:
    r
    (Read),
    w
    (Write),
    c
    (Clear value)。
  2. FlagType: ユーザー定義の列挙体などを指定 (
    flag_type = MyEnum
    )。
    • デフォルトは幅に合わせた基本型(例:6 ビット ->
      u8
      )。
  3. DontShift: 絶対値で扱いたい場合の設定 (
    dont_shift
    )。
  4. Helper Attributes: 生成されるコードのカスタマイズ。
#[bitfields]
struct MyFlags {
    #[flag(r)] // リードオンリー、オフセット 0-2
    a: B2,
    
    #[flag(rwc(10))] // レッド・ライト・クリア(値 10)、オフセット 3-7
    b: B5,
}

マクロの実装プロセス

設計思想:スモールステップで進める

実装を開始する前に、単純な例から機能を手作業で作成し、それを一般化すること。

1. 初期のビット演算ロジック

  • マスキング: フラグ以外のビットをゼロにする。
  • シフト:
    • リード時:フラグ位置へ右シフト (
      >>
      )。
    • ライト時:フラグ位置へ左シフト (
      <<
      )。
// マスク生成(全 1 から必要ない分を除去)
let mask = (u8::MAX >> (u8::BITS - width as u32)) << offset;

// リード値の取得
let val = (raw_value & mask) >> offset;

2. データ構造と定義

  • FlagAttribute
    :
    r
    ,
    w
    ,
    c
    ,
    flag_type
    ,
    dont_shift
    などを格納。
  • FlagMeta
    : ビット幅 (
    width
    ) と表現型 (
    repr_ty: u8/u16/...
    ) を格納。
  • BitField
    : 単一のフラグフィールドのメタ情報。

3. 属性解析(Parse)

  • syn::Attribute
    Meta
    パートから情報を抽出。
  • フォーク (
    input.fork()
    ) を用いて、成功/失敗を問わずストリームの正確な位置に戻せるようにする。
// 簡略化されたパーサーの概念実装
fn try_parse<T>(input: ParseStream, ..) -> Option<syn::Result<T>> {
    let fork = input.fork();
    match fork.parse::<T>() {
        Ok(parsed) => {
            // 成功:元の位置に戻し、解析されたデータを返す
            Some(Ok(parsed))
        },
        Err(_) => {
            // 失敗:エラーカウンター増やし、何もしない(フォークを放棄)
            None 
        }
    }
}

4. 構造体の解析と変換

  • syn::ItemStruct
    を解析し、フィールドごとに情報を抽出。
  • コメント属性(
    #[doc]
    )と Flag 属性を分離。
  • フィールドのオフセットを動的に計算し続ける。
impl<'a> BitField<'a> {
    pub fn new(field: &Field, offset: usize) -> syn::Result<Self> {
        // ... 属性解析、メタデータ抽出 ...
        Ok(BitField { 
            attr, vis, name, meta, offset, .. 
        })
    }
}

5. コード生成(Quote)

  • チェック関数: リリースビルドでは無効だが、デバッグ用で値の範囲を確認。
  • アクセサ生成:
    get_
    ,
    set_
    ,
    clear_
    を自動生成。
    • bool
      型の場合、
      is_*
      に名前を変更する。
  • 安全な参照: 完全修飾名(例:
    ::core::ptr::read_volatile
    )を使用し、コンパイル時エラーを回避。
// 簡略化された生成ロジック
impl<'a> BitFields<'a> {
    fn fn_read(&self, field: &BitField) -> TokenStream2 {
        // ... チェック処理、シフト処理 ...
        quote! {
            #[inline]
            pub fn get_#field_name(&self) -> #ty {
                unsafe { 
                    let val = self.0 as #repr_ty;
                    let mask = ...;
                    ((val & mask) >> offset) as #ty
                }
            }
        }
    }
}

6. 補助機能の実装

  • From
    Trait
    : 構造体と内部型 (
    u8
    など) の相互変換。
  • Debug
    Trait
    : フラグ状態を可視化するためのフォーマッター。
  • Builder Pattern:
    flag1().flag2()
    のようなチェーン式設定の実装。

7. マクロ本体の統合

  • 入力を受け取り、解析(またはエラー生成)を行う。
  • 成功時はコードを返す。
#[proc_macro_attribute]
pub fn bitfields(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let struct_def = parse_quote!(struct MyStruct { fields: ... });
    // BitFields::try_from(&struct_def).map(|...| quote!{#bitfields})
    // .unwrap_or_else(|e| e.into_compile_error())
}

まとめ

  • プロシージャルマクロは、コンパイル時にコードを生成・置換する強力な機能。
  • syn
    quote
    を併用することで、複雑な構文ツリーの解析と再生成が容易になる。
  • ビット操作メタデータ駆動のデザインパターンによって、柔軟かつ安全なマクロを実装できる。

同じ日のほかのニュース

一覧に戻る →

2026/06/07 4:17

Ntsc-rs ~アナログテレビおよびVHS のアーティファクトを模倣するオープンソースのビデオエミュレーター~

## 日本語翻訳: ntsc-rs は、単純なカラーフィルターではなく、実際の送信符号化の原理に基づいた高度なアルゴリズムを使用して、ヴィンテージ NTSC テレビと VHS テープのアートファクトを本物らしく再現する無料のオープンソースツールです。マルチスレッドおよび SIMD 加速を実現した Rust で構築されており、超標準解像度でもリアルタイムで動画を処理し、ntscQT といった旧来のツールよりも優れたパフォーマンスを発揮します。そのアルゴリズムは composite-video-simulator、zhuker/ntsc、および ntscQT で開発されたものを採用しています。独立したアプリケーション、ウェブプラットフォーム、または Adobe After Effects、Premiere Pro、DaVinci Resolve、Hitfilm、Vegas(すべての OpenFX ソフトウェアと互換性あり)用のプラグインとして利用可能であり、歴史的正确性と現代の効率性を兼ね備えており、クリエイターがリアルなアナログの美学を現代的なワークフローに直接統合できるよう支援します。

2026/06/07 3:35

Meta、AI チャットボットの悪用によるInstagramアカウントの乗っ取りが数千件あったと確認

## Japanese Translation: Metaは、ハッカーがAIチャットボットのバグを悪用し、Instagramのアカウント20,000個以上を乗っ取ったという重大なセキュリティ不備を公表しました。このシステムは、パスワードリセット用として設計されたものの、誤って認証コードを受け取るメールアドレスを登録済みのアカウント所有者に限定せず、任意のメールアドレスに入力した者に送信してしまっていました。これにより、無許可ユーザーが数日以内にして完全なアクセス権を引き渡すことができました。脆弱性は二段階認証を行っていないアカウントを狙い、4月中旬から最近まで活性していました。 この侵害事件は、人工知能(AI)に対する大幅な投資が行われている時期に発生しており、人員削減を踏まえ、イノベーションとセキュリティのバランスに関する懸念が浮上しています。Metaはこの乗っ取られたボットを停止し、コードを修正済みですが、同社は直接メッセージやプロフィール情報など機密データの暴露があったことを認めています。メイン州在住の30人が直接通知を受けたものの、同社は犯罪者がアクセスした個人データ総量について現在把握していないと確認しています。専門家は、この事件がAIの急速な展開に伴う重大なリスクを浮き彫りにしているとし、ユーザーに対して即時にパスワードのリセットと、認証済みチャネル経由での再認証を行ってアカウントのセキュリティを回復するよう求めており、またMetaは同様の再発を防ぐため、現在自社プラットフォーム上の他のチャットボットの調査を進めています。

2026/06/06 23:59

Zeroserve:eBPF を用いてスクリプト可能なゼロ設定 Web サーバー

## 日本語訳: ゼロサーブ(Zeroserve)は、最小限のセットアップで静的ファイルを配信できる軽量かつ高性能な HTTPS サーバーです。各サイトごとに単一のタールアーカイブのみを用いて動作を開始できます。改行・認証・レート制限・リバースプロキシなどの機能を実装するサンドボックス化されたミドルウェアとしてユーザー空間で動作する eBPF プログラムをサポートしています。パフォーマンスの主な特徴は、io_uring I/O インターフェースと即時コンパイル(Just-In-Time コンパイル)された eBPF スクリプトによる卓越したシングルスレッド速度です。Ryzen 7 3700X ベンチマークでは、小規模ファイルにおいて nginx を約 17% 上回り、Caddy よりも大幅に高性能であることが示されました。そのアーキテクチャはバイナリコードを共有する複数プロセスの並列実行によりスケールし、各インスタンスはシングルスレッドモデルの monoio ランタイムを使用します。BoringSSL を用いた高度な TLS 機能もサポートしており、TLS 1.3、暗号化された ClientHello(ECH)、SNI サーティフィケート選択、JA4 フィンガープリント、および ECH リレーモードを含みます。運用上の利点として、原子スワップ(SIGHUP)によるホットリロードやダウンタイムなしの瞬時の構成更新が可能です。eBPF プログラミングモデルは、ヘッダー操作やテンプレート置換を行うために共有されるパーリクエストメタデータマップを持つ、ファイル名順ソートされたスクリプトチェーンを使用します。ヘルパー関数はリクエスト検査・変異、暗号処理(SHA-256、HMAC、base64)、JSON 処理、トークンバケット方式のレート制限、AWS SigV4、および XChaCha20-Poly1305 クッキーを用いた OIDC ログインをカバーします。デフォルト設定では eBPF インスタンスあたりのメモリ使用量の上限が 256 KB で、他の接続を止めることを防ぐためにプリエンプト間隔は 2 ms に設定されています。この間隔を増やすことで動的レスポンスのスループットをさらに向上させることが可能です。リバースプロキシとしての性能では、プーリングされた io_uring コネクションを用いて小規模応答において最先端の速度を発揮し、大規模ボディにおいては nginx と競合するレベルのパフォーマンスを示します。全体として、このサーバーは Lua や Perl などの遅いインタプリターに依存せず、ユーザー空間内で直接低レイテンシーとより細かいセキュリティ制御を提供します。

ゼロから Rust の手続き型マクロ (Procedural Macro) を構築する | そっか~ニュース