ProgrammingRustアプリケーション/ライブラリアーキテクト

Rustにおけるマクロとは何ですか?マクロの種類、手続き型と宣言型の違い、どちらを選択すべきか、使用時に発生する可能性のある危険について教えてください。

Hintsage AIアシスタントで面接を突破

回答

Rustでは、マクロはコンパイル時にコードを生成することを可能にし、メタプログラミング、ボイラープレートの削減、DSLの実装のための強力なツールを提供します。主なマクロの種類は以下の通りです:

  • 宣言型マクロ(マクロルール、macro_rules!): matchに似た構文を用いて定義され、テンプレート→置換の原理で動作します。最も一般的なものは macro_rules! です。
  • 手続き型マクロ: crate内の外部関数として宣言され、AST(TokenStream)を受け取り、修正されたコードを返します。#[derive]、属性型(#[some_macro])および関数型(custom_macro!())に分けられます。

宣言型はテンプレートコードに対して使いやすく、手続き型は構文とトークン解析に対するより多くの制御を提供します。

宣言型マクロの例:

macro_rules! vec_of_strings { ($($x:expr),*) => { { let mut v = Vec::new(); $(v.push($x.to_string());)* v } }; } let v = vec_of_strings!("a", "b"); // => Vec<String>

手続き型マクロ(deriveの例):

#[derive(Debug, Clone)] struct MyStruct; // self derive Debugはstdにおいて手続き型マクロで実装されている。

カーブの質問

Rustのマクロは構文的に無効なコードや実行時エラーを持つコードを生成できますか?

回答: はい、マクロは作成時に展開の正当性を確認しないため、エラーはマクロがコンパイラによって置換された後にのみ現れることがあります。宣言型マクロは、明白でない構文エラーを引き起こす可能性があります。手続き型マクロは、不正確または脆弱なコードを生成する可能性があるため、慎重にテストすることが重要です。

例:

macro_rules! make_error { () => { let x = ; // マクロを使用する際に構文エラーが発生します } }

トピックの微妙さからの実際のエラーの例


ストーリー

大規模プロジェクトでボイラープレートを削減するためにmacro_rules!を使用したが、すべてのパターンをカバーしなかった。ユーザーがマクロにサポートされていない式を渡したため、理解しにくいコンパイルエラーが発生し、その原因を特定するのが難しかった。


ストーリー

手続き型マクロをクレート間で移動する際にTokenStream APIのバージョンの不整合の問題が発生し、そのためIDEがフリーズし、エラーはno_stdビルドでのみ現れた。


ストーリー

手続き型マクロを用いて設定用のDSLを作成した際に、入力トークンの安全でない解析(型の検証なし)を実装したため、ランタイムで奇妙なバグや脆弱性が発生し、新しい機能を正しくデプロイできなくなった。