メタプログラミングにより、プログラムは他のプログラムの生成や変換に関与します。コンパイル時メタプログラミングは作業の一部をビルドフェーズに移動しますが、異なるメカニズムは異なる対象を操作します。あるものはプリプロセストークンを処理し、あるものは構文データを処理し、あるものは定数を計算し、あるものはバイトコードを変更します。
理解する際は、まず以下の3つの問いを立ててください。メタプログラムが受け取るのは構文なのか、型チェック済みの値なのか。それはどのフェーズで実行されるのか。生成物はコードなのか、データなのか、それとも追加の振る舞いなのか。
まず入出力を区別する:構文の生成、評価、振る舞いの織り込み
トークン / 構文データ
マクロ展開
新しい構文 → 後続のチェック
定数式
コンパイル時評価
値 / 静的データ
プログラム + マッチルール
ウィービング
追加の振る舞いを含むプログラム
変換はビルドフェーズで発生しますが、生成された振る舞いやデータにランタイムコストがかからないわけではありません。
なぜ通常の関数では不十分なのか
通常の関数はランタイム計算の抽象化に適しており、通常、呼び出し元のフィールド名を知る必要はなく、新しい宣言を自由に生成することもできません。「構造体の各フィールドに対してシリアライズコードを生成する」や「型パラメータに基づいて新しい型を構築する」といったニーズがある場合、通常の関数呼び出しの能力を超えたものが必要になります。
しかし、メタプログラミングは展開、診断、ビルドの複雑さを増大させます。通常の関数、ジェネリクス、または組み合わせで要件が明確に表現できる場合、それらは通常、デバッグが容易です。選択基準は、どのメカニズムが強そうに見えるかではなく、どのような情報を操作する必要があるかであるべきです。
Cマクロ:プリプロセストークンの展開
Cプリプロセッサはプリプロセストークンのルールに従って置換を行います。これは無規則な文字列置換ではなく、後続の完全な式の意味や型を理解するわけではありません。条件付きコンパイル、文字列化、トークンの連結などの用途をサポートしています。
以下の x、y、z のステートメントは関数体内に配置されています。これはマクロ展開のローカルな例です。
#define PICK_MAX ( a , b ) ( ( a) > ( b) ? ( a) : ( b) )
int x = 3 ;
int y = 2 ;
int z = PICK_MAX ( x+ + , y ) ;
括弧は一部の問題を解決しますが、繰り返し評価の問題は解決しません。比較で x++ が1回使用され、選択された分岐でもう一度 x++ が実行されます。引数に関数呼び出しが含まれている場合、副作用が重複して発生する可能性があります。固定型の比較については、通常のインライン関数により、引数の評価が1回に抑えられ、型チェックも行われます。展開の境界については GCC マクロの落とし穴 を参照してください。
同じ呼び出し元で「2回展開されること」と「引数の評価が1回」を比較する
以下を evaluate-once.c として保存します。2つのテストはどちらも x=3 から始まり、マクロでは x=5、結果 4 になります。関数では x=4、結果 3 になります。この例では、未定義の順序で x を複数回変更しないようにし、繰り返し評価を未定義の動作で説明することを避けています。
#include < stdio.h>
#define PICK_MAX ( a , b ) ( ( a) > ( b) ? ( a) : ( b) )
static inline int max_int ( int a , int b ) { return a > b ? a : b; }
int main ( void ) {
int x = 3 , y = 2 ;
int by_macro = PICK_MAX ( x+ + , y ) ;
if ( x != 5 || by_macro != 4 ) return 1 ;
x = 3 ;
int by_function = max_int ( x+ + , y ) ;
if ( x != 4 || by_function != 3 ) return 1 ;
printf ( " macro=%d function=%d \n " , by_macro, by_function) ;
return 0 ;
}
cc -Wall -Wextra evaluate-once.c -o evaluate-once を実行すると、出力は macro=4 function=3 となります。cc -E で展開を確認すると、x++ がどの2つの分岐位置に現れるかを見ることができます。インライン最適化により関数呼び出しのオーバーヘッドは消除されますが、関数の引数は依然として言語のセマンティクスに従って評価され、マクロのような繰り返し評価にはなりません。GCC 副作用の重複
1回の評価であっても、複数の引数間に固定された評価順序があるわけではありません。2番目の引数も x を読み書きする場合、依然として C の評価順序の規則に従う必要があります。順序を明確にしたい場合は、まず独立したステートメントで一時値を計算し、その後で関数を呼び出してください。この固定型のマクロを関数で置き換えることで解決するのは、引数のコピーの問題であり、すべての式に対して自動的に順序を付けるわけではありません。
括弧は正しく書いているのに、なぜ x はさらに1回増加するのか?
注目点は戻り値だけでなく、x++ を読み取るたびに x の古い値と新しい値にも向けられます。初期状態で x>y の場合、比較で1回読み取られ、選択された分岐でもう1回読み取られます。x≤y の場合、2番目の x++ の分岐は実行されません。関数バージョンでは、常に最初に引数の値を1つ取得し、それを関数内で再利用します。この対照は、C の条件演算子の順序の境界を維持しており、未定義の動作に頼って差異を生じさせていません。
図を準備しています 再読み込み
括弧は正しく書いているのに、なぜ x はさらに1回増加するのか? マクロがコピーするのは評価されていない式であり、比較の後に選択された x++ はまだ実行されます。関数はまず引数を値に評価し、その値を関数内で再利用します。分岐が選択されなくても、2回目の実行が条件なしで行われるわけではありません。
{"id":"macro-evaluation-count","title":"括弧は正しく書いているのに、なぜ x はさらに1回増加するのか?","summary":"マクロがコピーするのは評価されていない式であり、比較の後に選択された x++ はまだ実行されます。関数はまず引数を値に評価し、その値を関数内で再利用します。分岐が選択されなくても、2回目の実行が条件なしで行われるわけではありません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"x\" data-i18n=\"x\"></label><output for=\"x\" id=\"x-value\"></output><input id=\"x\" type=\"range\" min=\"0\" max=\"5\" value=\"3\" step=\"1\"></div><div class=\"control\"><label for=\"y\" data-i18n=\"y\"></label><output for=\"y\" id=\"y-value\"></output><input id=\"y\" type=\"range\" min=\"0\" max=\"5\" value=\"2\" step=\"1\"></div><div id=\"expansion\" class=\"source\"></div><div id=\"evaluations\" class=\"table\"></div><div class=\"metrics\"><div><span data-i18n=\"macro\"></span><strong id=\"macro\"></strong></div><div><span data-i18n=\"function\"></span><strong id=\"function\"></strong></div></div><p id=\"count\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#expansion{min-height:5.4em}.metrics strong{font-size:18px}#evaluations .tr{grid-template-columns:1fr 1fr}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function draw(){const x=+$('#x').value,y=+$('#y').value,branch=x>y,macroResult=branch?x+1:y,macroX=x+(branch?2:1),fn=Math.max(x,y);set('x-value',x);set('y-value',y);$('#expansion').innerHTML='((<mark>x++</mark>) > (y) ? ('+(branch?'<mark>x++</mark>':'x++')+') : (y))\\ncondition: '+x+' > '+y+' = '+branch;table('evaluations',[['compare','read '+x+'; x→'+(x+1)],['selected branch',branch?'read '+(x+1)+'; x→'+(x+2):'read y='+y]]);set('macro',macroResult+' / '+macroX);set('function',fn+' / '+(x+1));set('count',t('count')+': '+(branch?2:1)+' / 1');$('#macro').dataset.values=[macroResult,macroX].join(',');}$('#x').oninput=draw;$('#y').oninput=draw;draw();","audio":false,"strings":{"count":"x++ の評価回数(マクロ / 関数)","function":"関数:結果 / 最終的な x","heading":"括弧は正しく書いているのに、なぜ x はさらに1回増加するのか?","intro":"PICK_MAX(x++, y) と max_int(x++, y) を比較する。初期値の x、y を変更し、マクロがどの分岐を選択し、実際の評価回数がどうなるか観察する。","macro":"マクロ:結果 / 最終的な x","reset":"リセット","scope":"固定された C の条件演算子のセマンティクス。x/y は 0..5 の範囲、2番目の引数には副作用がない。モデルには順序の決まらない重複した変更は含まれておらず、通常の関数の引数間に固定された評価順序があるとは主張しない。","scopeLabel":"モデルの範囲と規約","x":"初期 x","y":"固定 y"}}
Lispマクロ:入力は未評価のコード表現
Lisp のコード表現は、リストなどのデータとして操作できます。マクロは、通常の関数の引数のルールに従って評価されていない表現を受け取り、新しいコード表現を生成します。その後、言語がその展開結果を処理します。
( defmacro unless-demo ( condition &body body)
`( if ( not ,condition) ( progn ,@body) ) )
ここで condition と body は、生成された if 式に配置されます。それらが表すランタイムの動作は、マクロ展開時に実行されるわけではありません。テンプレート展開、アンクォート、連結はコードの構築に役立ちますが、マクロの作者は依然として変数キャプチャ、評価回数、展開環境、エラー位置を考慮する必要があります。コードがデータだからといって、スコープやセマンティクスの問題が消えるわけではありません。
Rustプロシージャルマクロ:TokenStream は型付き AST ではない
Rust のプロシージャルマクロは、コンパイル時に TokenStream を消費して生成します。マクロは syn などのライブラリを使用して構文構造に解析できますが、これは rustc が型推論を完了した後の内部 AST を取得したことを意味するものではなく、式が推論された型を普遍的に照会できるわけでもありません。
プロシージャルマクロには主に function-like、derive、attribute の3種類があり、展開の挿入や置換ルールが異なります。例えば derive は追加のエントリを生成し、impl の生成によく使われます。attribute は注釈付きのエントリを置き換えることができます。インターフェースとルールについては Rust リファレンス を参照してください。
struct Record<T> { value: T } に対してシリアライズ実装を生成すると仮定します。マクロはジェネリックパラメータと where 制約を保持し、有効なパスと境界を生成する必要があります。その後、後続のコンパイルフェーズで実装が型として正しいかどうかをチェックします。「マクロがフィールドの構文を知っていること」と「マクロがフィールドの型がすべての制約を満たしていることを証明すること」は異なります。
衛生性:展開後の名前が誰を指すか
衛生性は、生成された名前と呼び出し元の名前の予期せぬキャプチャを解決します。Rust の macro_rules! には、混合された位置の衛生ルールがあります。プロシージャルマクロは「本質的に完全に衛生である」と一般化することはできません。プロシージャルマクロの作者は、名前衝突やパス解決を処理する必要があります。例えば、適切な絶対パスを使用し、span を保持し、衝突しやすい補助的な名前を生成しないようにします。
衛生性は、1回の評価、借用の正しさ、または良好なエラー情報を自動的に保証するものではありません。マクロが生成するコードのこれらの性質は、個別に設計する必要があります。
生成された tmp が、呼び出し元の tmp を奪うことはないか?
同じスペルの tmp が、異なる宣言環境から来る可能性があります。下の図では、呼び出し元の宣言 C とマクロが生成した宣言 M を保持し、挿入された式と補助的な参照をそれぞれ追跡します。衛生ポリシーをオフにすると、呼び出し元も tmp と呼んでいる場合にのみキャプチャが発生します。名前の変更はこの例の衝突を回避できますが、すべてのマクロ呼び出しを安全にするための一般的な解決策ではありません。衛生ルールは、単に目立たない変数名を探すだけでなく、束縛のソースを維持すべきです。
図を準備しています 再読み込み
生成された tmp が、呼び出し元の tmp を奪うことはないか? 衛生性は、挿入された式が持つ宣言のアイデンティティを保持しつつ、生成された補助的な参照が生成された宣言を指すようにします。展開後のテキストに基づいて最近の名前検索を行うと、呼び出し元の変数を補助変数に誤って束縛する可能性があります。
{"id":"macro-hygiene-binding","title":"生成された tmp が、呼び出し元の tmp を奪うことはないか?","summary":"衛生性は、挿入された式が持つ宣言のアイデンティティを保持しつつ、生成された補助的な参照が生成された宣言を指すようにします。展開後のテキストに基づいて最近の名前検索を行うと、呼び出し元の変数を補助変数に誤って束縛する可能性があります。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"name\"><button data-mode=\"tmp\" data-i18n=\"same\"></button><button data-mode=\"value\" data-i18n=\"different\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"hygiene\" checked><span data-i18n=\"hygiene\"></span></label><p id=\"expansion\" class=\"source\"></p><svg id=\"bindings\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\"></svg><div class=\"metrics\"><div><span data-i18n=\"result\"></span><strong id=\"result\"></strong></div><div><span data-i18n=\"binding\"></span><strong id=\"binding\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#expansion{min-height:7.2em}.metrics strong{font-size:18px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}let name='tmp';function draw(){const good=$('#hygiene').checked||name!=='tmp';set('expansion','let '+name+' = 10; // C\\nwith_temp('+name+')\\n⇒ { let tmp = 1; // M\\n '+name+' + tmp; }');graph('bindings',[{name:'C',x:80,y:40,cls:good?'on':'',sub:name+'=10'},{name:'M',x:280,y:40,cls:'gold',sub:'tmp=1'},{name:'expr',x:80,y:150},{name:'tmp',x:280,y:150}],[[2,good?0:1,true],[3,1,true]]);set('result',good?11:2);set('binding',good?'C':'M');pressed(name);}document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{name=b.dataset.mode;draw()});$('#hygiene').onchange=draw;draw();","audio":false,"strings":{"binding":"expr の束縛","different":"value:異名","heading":"生成された tmp が、呼び出し元の tmp を奪うことはないか?","hygiene":"挿入された式の束縛アイデンティティを保持","intro":"教材のマクロ with_temp(expr) は let tmp=1; expr+tmp を生成する。呼び出し元の名前と衛生束縛を切り替え、同じスペルの2つがそれぞれ誰を指すか観察する。","name":"呼び出し元の名前","reset":"リセット","result":"式の結果(期待値 11)","same":"tmp:同名が発生","scope":"字句スコープを持つ教材言語。呼び出し元の変数の値は10、補助変数の値は1。宣言ID戦略を比較する。Rust、Lispなどの言語のそれぞれの完全なマクロ衛生ルールを装わない。","scopeLabel":"モデルの範囲と規約"}}
コンパイル時評価:値の計算
Rust の const fn は、条件を満たす定数コンテキストで実行できます。通常の呼び出し位置ではランタイムで実行することもでき、関数宣言に const があるからといって、毎回呼び出しがコンパイル時に行われるとは限りません。
const fn square_table ( ) -> [ u32 ; 4 ] {
let mut out = [ 0 ; 4 ] ;
let mut i = 0 ;
while i < 4 {
out[ i] = ( i * i) as u32 ;
i + = 1 ;
}
out
}
const SQUARES : [ u32 ; 4 ] = square_table ( ) ;
fn main ( ) {
assert_eq! ( SQUARES , [ 0 , 1 , 4 , 9 ] ) ;
}
定数コンテキストでは、有効な定数結果を得る必要があり、言語は実行可能な操作を制限します。配列の範囲外アクセスなどは、評価が必要な場合にコンパイルエラーとなります。許可される構文や参照の動作は、言語とバージョンの規定に従います。Rust 定数評価
C++ の constexpr、consteval、Zig の comptime の具体的な能力は異なり、同じインタプリタとして一律に記述すべきではありません。const 評価とビルドスクリプトやプロシージャルマクロの権限を混同しないでください。後者はビルド環境やファイルシステムにアクセスする可能性があります。
事前計算により計算時間がビルドフェーズに移動し、結果を生成物に配置できる可能性があります。また、バイナリのサイズ、コンパイル時のメモリ、キャッシュの負荷を増加させる可能性もあります。「コンパイル時の実行」は、すべてのコストがゼロになることを意味しません。
同じ const fn でも、呼び出しコンテキストによってエラーが露呈する段階が決まる
以下では、有効なペイロードの上限を 1024、固定ヘッダーを 8 バイトと定義します。定数 PACKET_SIZE の初期化はコンパイル時の評価で行う必要があります。main 内の args からの長さは、通常のランタイム入力に属します。
const fn packet_size ( payload : usize ) -> usize {
assert! ( payload < = 1024 , " payload too large" ) ;
payload + 8
}
const PACKET_SIZE : usize = packet_size ( 16 ) ;
fn main ( ) {
assert_eq! ( PACKET_SIZE , 24 ) ;
let runtime_input = std:: env:: args( ) .count ( ) ;
assert_eq! ( packet_size ( runtime_input) , runtime_input + 8 ) ;
}
引数なしで直接コンパイルして実行すると、通過します。定数初期化を packet_size(1025) に変更すると、コンパイラはこの定数を拒否しなければなりません。main の通常のパスで 1025 を渡す場合、プログラムはアサーションに到達した時点でパニックします。const fn の関数本体が2つのロジックに変わるわけではありません。変化するのは、現在のコンテキストが有効な定数結果を要求しているかどうかです。定数コンテキスト
使用位置 フェーズの義務 これに基づいて推論できないこと
const 初期化 定数評価規則の下で完了できる必要がある データに生成物のスペースコストがない
通常のランタイム呼び出し 通常の実行セマンティクスに従って値を生成するか失敗する コンパイラが定数畳み込みを全く行わない
ビルドスクリプトによるファイル読み取り ビルド時に独立したプログラムとして実行 ファイルの内容が自動的に宣言された依存関係になる
プロシージャルマクロによる式の生成 未コンパイルの構文を生成する 式がすでに型チェックを完了している
同じ const fn でも、エラーはどの段階で発生するのか?
フェーズの義務と最適化の機会は分離されるべきです。const 初期化では、コンパイルフェーズで有効な評価を完了する必要があります。通常のパスでは、実際に実行されるかどうかによってランタイムの結果が決まります。以下では、同じ packet_size 関数本体を保持し、呼び出しコンテキストと入力のみを変更し、エラーがどこから発生するかを観察しやすくしています。通常の呼び出しがオプティマイザによって事前計算されたとしても、それによって無条件に定数コンテキストの診断ルールに変更されるわけではありません。
図を準備しています 再読み込み
同じ const fn でも、エラーはどの段階で発生するのか? const 初期化では有効な定数を得る必要があります。通常の呼び出しは、関数名に const が含まれているからといって、コンパイル時の評価が必須になるわけではありません。通常のパスが実行されない場合、その呼び出しのランタイムパニックが突然発生することはありません。
{"id":"constant-evaluation-context","title":"同じ const fn でも、エラーはどの段階で発生するのか?","summary":"const 初期化では有効な定数を得る必要があります。通常の呼び出しは、関数名に const が含まれているからといって、コンパイル時の評価が必須になるわけではありません。通常のパスが実行されない場合、その呼び出しのランタイムパニックが突然発生することはありません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"context\"><button data-mode=\"const\" data-i18n=\"constant\"></button><button data-mode=\"runtime\" data-i18n=\"runtime\"></button></div><div class=\"control\"><label for=\"n\" data-i18n=\"n\"></label><output for=\"n\" id=\"n-value\"></output><input id=\"n\" type=\"range\" min=\"0\" max=\"1050\" value=\"1025\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"execute\" checked><span data-i18n=\"execute\"></span></label><p id=\"source\" class=\"source\"></p><div id=\"phases\" class=\"track\"></div><div class=\"metrics\"><div><span data-i18n=\"compile\"></span><strong id=\"compile\"></strong></div><div><span data-i18n=\"run\"></span><strong id=\"run\"></strong></div><div><span data-i18n=\"value\"></span><strong id=\"value\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#source{min-height:12.6em}.metrics strong{font-size:17px;min-height:3.2em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}let mode='const';function draw(){const n=+$('#n').value,valid=n<=1024,constant=mode==='const',executed=$('#execute').checked;$('#execute').disabled=constant;set('n-value',n);set('source','const fn packet_size(n) {\\n assert(n <= 1024); n+8\\n}\\n'+(constant?'const SIZE = packet_size('+n+');':'if execute { packet_size(input_n); }'));const compileOK=!constant||valid,run=constant?(valid?t('embedded'):'—'):!executed?t('notrun'):valid?'return':'panic';set('compile',t(compileOK?'ok':'fail'));set('run',run);set('value',valid&&(constant||executed)?n+8:'—');cells('phases',[{text:'source',cls:'on'},{text:constant?'const eval':'compile',cls:compileOK?'on':'gold'},{text:'runtime',cls:constant?'empty':executed?'on':'empty'}]);$('#compile').dataset.valid=String(compileOK);pressed(mode);}$('#n').oninput=draw;$('#execute').onchange=draw;document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;draw()});draw();","audio":false,"strings":{"compile":"コンパイルフェーズ","constant":"const 初期化","context":"呼び出しコンテキスト","embedded":"埋め込まれた定数","execute":"この呼び出しまで実行する","fail":"定数評価エラー","heading":"同じ const fn でも、エラーはどの段階で発生するのか?","intro":"packet_size(n) はまず n≤1024 をチェックし、n+8 を返す。呼び出しコンテキストと入力を選択し、コンパイル時の義務と通常のランタイム呼び出しを区別する。","n":"ペイロードの長さ n","notrun":"未実行","ok":"コンパイル成功","reset":"リセット","run":"ランタイムフェーズ","runtime":"通常のランタイム呼び出し","scope":"本文の Rust const fn のフェーズの義務をシミュレート。payload は 0..1050。ブラウザで rustc を実行しているわけではない。オプティマイザは通常の呼び出しを畳み込む可能性があるが、ここで定義された観測可能なセマンティクスを変更することはできない。","scopeLabel":"モデルの範囲と規約","value":"得られた値"}}
テンプレートとジェネリクス:パラメータがプログラムの構造を決定することもできる
ジェネリクスの型パラメータはチェックに参加します。一部の言語では値パラメータ、型レベルの計算、またはパラメータに基づくコードのインスタンス化もサポートしています。単一化により、異なるインスタンスが専用の実装を得ることができますが、インスタンスの数とコンパイルコストが増加します。型消去や辞書渡しは、異なるランタイム表現と呼び出し方法を採用します。
これらはマクロと組み合わせることができます。マクロがまずジェネリクスを含む実装を生成し、コンパイラがチェックを行い、戦略に基づいてコードを生成します。すべてのジェネリクスをマクロと呼んだり、すべてのメタプログラミングを AST 置換と呼んだりすると、フェーズの違いが隠蔽されてしまいます。
AOPウィービング:プログラム内のジョイントポイントを選択して振る舞いを追加する
AspectJ は pointcut を通じてジョイントポイントの集合を記述し、advice を通じて実行する振る舞いを指定します。call と execution がマッチする対象は異なり、メソッド実行のジョイントポイントを一律に呼び出し前の AST 挿入として描画してはいけません。
AspectJ はソースコードのコンパイルと、既存のクラスファイルへのウィービングなどのパスをサポートしており、生成物は振る舞いが織り込まれたバイトコードになります。ロード時ウィービングも存在します。これは「コンパイラが Java AST を修正するだけ」という概念に限定されるものではありません。AspectJ コンパイラとウィーバー
事前ウィービングにより、いくつかのプロキシの境界を回避できますが、advice 内のログ、タイマー、チェックは依然としてランタイムで実行され、動的なジョイントポイントの条件にはコストがかかる可能性があります。セルフ呼び出しがインターセプトされるかどうかは、メカニズム、マッチルール、ウィービングの範囲に依存し、「すべての呼び出しをカバーし、ランタイムオーバーヘッドがゼロである」という一般的な結論を出すことはできません。
生成結果の再現性は、出力に影響を与えるすべての入力に依存する
明示的な入力
スキーマ + ジェネレータのバージョン + オプション
決定論的生成
固定されたフィールド順序と命名ルール
生成コード
後続の解析、型チェック、コンパイル
暗黙的な入力:現在のディレクトリ、環境変数、時刻、または非順序の走査
出力を変更する可能性がある場合、ビルドモデルに組み込むか、生成ロジックから排除する必要がある
生成の成功と生成物の正しさは別のステップである。コンパイルが通った後、生成されたインターフェースの実際のセマンティクスを検証する必要がある。
ビルドキャッシュはジェネレータの実際の依存関係を見える化する必要がある
例えば、スキーマから Record のシリアライズ実装を生成する場合、フィールドの順序はスキーマから、命名ルールはジェネレータのバージョンから、出力モードは設定スイッチから来ます。ジェネレータのソースファイルのみを依存関係としてリストすると、スキーマを変更した後に古い出力を使い続ける可能性があります。スキーマのみを依存関係としてリストすると、命名ルールをアップグレードしても再ビルドされない可能性があります。
逆に、ジェネレータが毎回実行時に無条件に現在の時刻を追加する場合、すべてのビジネス入力が同じであっても、生成結果は変化し、キャッシュとコードレビューにノイズをもたらします。まず、時刻が生成物のセマンティクスに属するかどうかを決定する必要があります。単なるログであれば、ビルドログに残しておくだけで十分です。
変化 合理的な期待 確認が必要な点
フィールドの追加 対応する生成宣言の変化 入力依存関係によりジェネレータが再実行されるか
ジェネレータルールのアップグレード 同じスキーマが新しいルールで生成される ツールのバージョンがキャッシュキーに含まれるか
ビジネス入力の変化なし 生成内容は安定している 順序、乱数、暗黙的な環境
無効な型の組み合わせが生成される コンパイル診断がドメイン入力に指し示す ソース位置または生成マニフェストが保持されているか
コンパイル時の計算にも規模の限界があります。N 個のジェネリックインスタンスそれぞれが B バイトのコードを生成する場合、コードスペースはインスタンス数に応じて増加する可能性がありますが、リンカの折りたたみ、最適化、共有戦略により最終的なサイズは変化します。生成物とビルドリソースを確認する必要があり、ソースコードで何行少なく書けたかだけで、コンパイル時メタプログラミングが総コストを必ず削減すると認定してはいけません。
エンジニアリングレビュークイックチェック
要件 検討可能なメカニズム 確認すべき点
少量の重複計算 関数、ジェネリクス すでに十分に表現できているか
フィールドごとの実装生成 derive / プロシージャルマクロ ジェネリクス、パス、エラー span、生成コードのチェック可能性
事前生成された定数テーブル 定数評価 許可される操作、コンパイルコスト、生成物のサイズ
型と値に基づく専用構造の生成 テンプレートまたは型レベルのメカニズム インスタンスの膨張、制約の診断
横断的な振る舞い ウィービングまたはプロキシ ジョイントポイントのセマンティクス、カバー範囲、ランタイムの副作用
検証では、通常の展開だけでなく、無効な入力、同名の変数、副作用を持つ引数、ジェネリックの境界、および変更後のインクリメンタルビルドが正しいかどうかを失効させるかどうかを確認する必要があります。生成結果には追跡可能な位置と安定した順序があり、ビルド環境の暗黙的な入力が再現困難な生成物をもたらすことを避けるべきです。
参考
Keywords: metaprogramming, macro, TokenStream, hygiene, const fn, constexpr, templates, AspectJ, weaving