async fn を呼び出すと Future が構築され、その関数本体はポーリングされることで進行します。Future は完了していない計算を記述するものであり、独立したスレッドを内包しません。実行時(Executor)がいつポーリングするかを決定し、非同期 I/O などの待機ソースは、処理を継続できる場合に実行時に通知を行います。
.await は必ずしも一度のスレッド切り替えを意味しません。子 Future が即座に Ready を返す場合、現在のポーリング処理はそのまま継続でき、1回のポーリング内で複数の await を完了させることも可能です。
初回ポーリング 子 Future が Ready になるか、実際に待機が必要な位置に到達するまで実行 1 Pending を返す 継続に必要な状態を保存し、対応するウェイクアップ経路を確立 2 ウェイクアップ通知を受信 再 poll を要求。複数の wake は統合可能 3 再ポーリング 4 Future の進行:必要に応じてのみ再ポーリング Pending のまま継続する場合もあれば、Ready(output) を返す場合もある 1回の await が必ずブロックするわけではありません。wake は結果を伴わず、次のポーリングで完了したことを保証する ものではありません。
Future の構築と関数本体の実行
use std:: { cell:: Cell, future:: Future, task:: { Context, Poll, Waker} } ;
async fn calculate ( started : & Cell< u32 > ) -> u32 {
started.set ( started.get ( ) + 1 ) ;
let left = std:: future:: ready( 20 ) .await;
let right = std:: future:: ready( 22 ) .await;
left + right
}
fn main ( ) {
let started = Cell:: new( 0 ) ;
let future = calculate ( & started) ;
assert_eq! ( started.get ( ) , 0 ) ;
let mut future = std:: pin:: pin! ( future) ;
let mut context = Context:: from_waker( Waker:: noop( ) ) ;
assert_eq! ( future.as_mut ( ) .poll ( & mut context) , Poll:: Ready( 42 ) ) ;
assert_eq! ( started.get ( ) , 1 ) ;
}
関数本体は初回の poll 呼び出し時に実行されます。2つの ready Future は即座に完了するため、関数全体が1回の poll で 42 を返します。ここでは noop Waker を使用していますが、これは待機を必要としないサンプルであるためです。これはタスクの実行をスケジュールせず、実際の非同期 I/O を駆動するために使用することはできません。Waker::noop
呼び出し式の引数評価は、Future の構築時に行われます。例えば calculate(make_counter()) の場合、make_counter の呼び出しは自動的に遅延されません。async 関数本体の実行のみがポーリングによって駆動されます。async move を使用した場合でも、キャプチャされた値の所有権はブロック作成時に取得され、その後一度も poll されなかったとしても同様です。
コンパイラはどのような状態を保存するか
コンパイラは async 関数を、一時停止と再開が可能な匿名の状態機械として表現します。これには、現在待機している子 Future と、後続の処理で使用する必要があるローカル値が含まれます。read_file を待機している場合、通常、すでに取得済みだが実際にはまだ生成されていないファイル内容ではなく、読み込み操作の状態が保存されます。
状態機械の具体的なレイアウトは実装の詳細であり、「各 await に対応する同名の列挙型バリアントが必ず存在する」ということを ABI として扱うことはできません。大きな値が一時停止点をまたぐと Future のサイズが大きくなる可能性があるため、ライフサイクルを早期に終了させることで状態のサイズを削減できる場合があります。
poll と Waker の契約
Future::poll は Pin<&mut Self> と Context を受け取り、Poll<Output> を返します。Pending は結果がまだ得られないことを示し、関連する実装はタスクが継続できる場合に備えてウェイクアップ経路を確立する必要があります。この作業はポーリングされている子 Future に委譲でき、各層で Waker を保存する必要はありません。
wake はスケジューリングへのヒントであり、マージされる場合もあれば、poll が Pending を返す前に発生することもあります。独自の待機プリミティブを実装する際は、状態のチェックと通知の登録の間の競合状態を正しく処理する必要があります。「まず結果がないか確認し、その後でコールバックを登録する」という順序では、その間に完了したイベントを見逃す可能性があります。
Ready を返した後、呼び出し側は完了した Future を任意にポーリングしてはいけません。具体的な型ではパニックが発生したり、インターフェースで定義された他の動作が発生したりする可能性があり、これを Iterator の next を継続するプロトコルのように扱うべきではありません。Future
Ready は完了を意味するが、ビジネスロジック上の成功を意味するわけではない
Future の Output は Result<T, E> になり得ます。したがって、Poll::Ready(Err(error)) は計算が完了し、エラーを返したことを意味します。Pending は最終的な結果がまだ得られないことを意味します。実行時は Err を「準備ができていない」と見なしてポーリングを続けるべきではありません。
poll の結果 スケジューラ層の意味 呼び出し側の処理
Pending 現在完了できない 通知を待ってから再度進行
Ready(Ok(value)) 完了済み ビジネス結果を使用
Ready(Err(error)) 完了済み 伝播、報告、またはビジネスロジックに基づいて操作を再作成
use std:: { future:: Future, task:: { Context, Poll, Waker} } ;
fn main ( ) {
let mut future = std:: pin:: pin! ( async {
" bad" .parse:: < u32 > ( )
} ) ;
let mut context = Context:: from_waker( Waker:: noop( ) ) ;
assert! ( matches! ( future.as_mut ( ) .poll ( & mut context) , Poll:: Ready( Err ( _ ) ) ) ) ;
}
なぜ最初の Waker だけを保存すれば十分でないのか
同じ Future が後続の poll で異なるタスクの Waker を受信する可能性があります。例えば、異なる外側の組み合わせによって駆動されている場合などです。待機プリミティブはプロトコルに従って現在の通知先を更新する必要があり、初回の登録後はずっと変わらないと仮定してはいけません。wake 自体は結果を伴わないため、結果は適切な同期された共有状態に保存され、その後 poll によって読み取られる必要があります。Future::poll
推論が容易な待機プリミティブでは、「結果が存在するか」と「現在の Waker」を同じロックで保護します。消費者はロック内で結果をチェックし、未完成であれば Waker を登録します。生産者はロック内で結果を書き込み、Waker を取得してロックを解放し、wake を呼び出します。これにより、完了イベントがチェックと登録の間に発生して誰も通知しないという事態を防ぐことができます。実際のライブラリでは、クローズ、重複する完了、キャンセル、公平性の処理も必要です。この順序の説明を、チャネルが完全に実装されたものとして扱わないでください。
wake は実行そのものではない
外部の準備完了、wake、poll を分けると、await を独立して動く関数だと誤解しにくくなります。Pending にしてから I/O だけを完了すると、再実行可能でも Future はまだ結果を返していません。wake の回数と poll の回数も一致する必要はありません。
図を準備しています 再読み込み
wake は実行そのものではない Future は poll で進み、wake は後の poll を要求します。Pending は自動的なバックグラウンド実行ではありません。
{"id":"rust-future-wake-poll","title":"wake は実行そのものではない","summary":"Future は poll で進み、wake は後の poll を要求します。Pending は自動的なバックグラウンド実行ではありません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"poll\" data-i18n=\"poll\"></button><button id=\"ready\" data-i18n=\"ready\"></button><button id=\"wake\" data-i18n=\"wake\"></button><button id=\"drop\" data-i18n=\"drop\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><div class=\"metrics\"><div><span data-i18n=\"queue\"></span><strong id=\"queue\"></strong></div><div><span data-i18n=\"io\"></span><strong id=\"io\"></strong></div><div><span data-i18n=\"count\"></span><strong id=\"count\"></strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></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:550;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:550 20px ui-monospace,monospace}.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:ui-monospace,monospace;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 ui-monospace,monospace;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 ui-monospace,monospace;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 ui-monospace,monospace}.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 ui-monospace,monospace;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:ui-monospace,monospace}.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.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px ui-monospace,monospace;padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 ui-monospace,monospace;overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.chart-wrap{margin:18px 0}.chart-label{font-size:12px;color:var(--muted);min-height:3.2em}.curve{display:block;width:100%;height:auto;max-height:320px;overflow:visible}.curve text{font:11px ui-monospace,monospace;fill:var(--muted)}.gridline{stroke:var(--rule);stroke-width:1}.metrics strong{font-size:20px;min-height:3.2em}.formula{font:12px/1.8 ui-monospace,monospace;min-height:9em}.readout{min-height:8em}.presets button{flex-basis:30%}.scope p{overflow-wrap:anywhere} .unit-note{font-size:12px;color:var(--muted);margin:10px 0}\n\n.metrics span{word-break:break-all;line-break:strict}.metrics>div{padding-right:6px}\n\n.cell{min-height:72px}.metrics strong{min-height:3.6em}\n\n#state{min-height:3em;font-size:16px}.metrics strong{font-size:15px;min-height:4em}","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).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';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 band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));let state='fresh',io=false,q=false,c=0;function draw(){set('state',t(state));set('queue',t(q?'queued':'empty'));set('io',t(io?'available':'waiting'));set('count',c);$('#poll').disabled=['done','gone'].includes(state);$('#ready').disabled=io||['done','gone'].includes(state);$('#wake').disabled=state!=='pending';$('#drop').disabled=state==='gone';set('explanation',t('explain'));document.body.dataset.state=state;document.body.dataset.queued=q;}for(let id of ['poll','ready','wake','drop','reset'])$('#'+id).onclick=()=>{if(id==='poll'){c++;q=false;state=io?'done':'pending'}if(id==='ready'){io=true;if(state==='pending')q=true}if(id==='wake')q=true;if(id==='drop'){state='gone';q=false}if(id==='reset'){state='fresh';io=false;q=false;c=0}draw()};draw();","audio":false,"strings":{"available":"完了","count":"poll 回数","done":"Ready(Ok(data))","drop":"Future を破棄","empty":"空","explain":"poll で Pending にし、I/O を完了させるとキューに入りますが、次の poll まで Ready は返しません。","fresh":"未実行","gone":"破棄済み","heading":"wake は実行そのものではない","intro":"poll、外部の準備完了、スケジューリングを別々に操作します。","io":"I/O の状態","pending":"Pending:Waker を保存","poll":"一度 poll","queue":"実行待ちキュー","queued":"poll 待ち","ready":"外部 I/O を完了","reset":"リセット","scope":"一度だけの I/O のモデルです。重複 wake は統合し、手動の追加 poll も可能ですが、実際の実行順は予測しません。","scopeLabel":"モデルの範囲と前提","state":"Future の状態","waiting":"未完了","wake":"再度 wake"}}
実行時不要の手動ポーリングの例
以下の Future は、最初の要求で再スケジューリングを要求し、2回目で結果を返します。この例では安全な Wake インターフェースを使用しており、RawWaker を手書きする必要はありません。
use std:: future:: Future;
use std:: pin:: Pin;
use std:: sync:: { Arc, atomic:: { AtomicUsize, Ordering} } ;
use std:: task:: { Context, Poll, Wake, Waker} ;
struct YieldOnce ( bool ) ;
impl Future for YieldOnce {
type Output = u32 ;
fn poll ( mut self : Pin< & mut Self > , cx : & mut Context< '_ > ) -> Poll< u32 > {
if self .0 {
Poll:: Ready( 42 )
} else {
self .0 = true ;
cx.waker ( ) .wake_by_ref ( ) ;
Poll:: Pending
}
}
}
struct WakeCount ( AtomicUsize) ;
impl Wake for WakeCount {
fn wake ( self : Arc< Self > ) { self .0. fetch_add ( 1 , Ordering:: Relaxed) ; }
fn wake_by_ref ( self : & Arc< Self > ) { self .0. fetch_add ( 1 , Ordering:: Relaxed) ; }
}
fn main ( ) {
let count = Arc:: new( WakeCount( AtomicUsize:: new( 0 ) ) ) ;
let waker = Waker:: from( count.clone ( ) ) ;
let mut context = Context:: from_waker( & waker) ;
let mut future = std:: pin:: pin! ( YieldOnce( false ) ) ;
assert! ( future.as_mut ( ) .poll ( & mut context) .is_pending ( ) ) ;
assert_eq! ( count.0. load ( Ordering:: Relaxed) , 1 ) ;
assert_eq! ( future.as_mut ( ) .poll ( & mut context) , Poll:: Ready( 42 ) ) ;
}
これは決定論的な2回のポーリングのデモであり、汎用の Executor ではありません。実際の Executor はタスクキューと通知の競合を管理する必要があり、すべての Pending を無条件の park に対応させることはできません。
Pin が固定するのは指し示される値
状態機械には内部データへの参照が含まれる可能性があるため、適切な pinning 契約に基づいてアドレスの安定性が確立されると、!Unpin の固定された値を正しくデストラクトするまで、勝手にムーブすることはできません。
Pin はコンパイラがすべての値に課す魔法のアドレスロックではなく、安全な API の制限付き操作と unsafe 実装の義務によって維持される契約です。Pin<Box<F>> というポインタラッパー自体はムーブ可能ですが、それが指す F の位置が保たれている限りです。F: Unpin の場合、Pin は同様のムーブ制限を課しません。Pin
初回の固定前、Future は通常ムーブ可能です。すべての async Future が実際に自己参照を持つわけではありませんし、ローカル変数に String が1つあるからといって、必ずしも String オブジェクトのムーブによってヒープバッファが移動するわけではありません。オブジェクトとそれが指すストレージを区別する必要があります。
ラッパの移動と固定された値の移動は違う
所有ハンドルだけを動かすと、アドレス依存の F はヒープの A に残ります。F 自体を動かす反実仮想で、内部アドレスが自動修正されない理由を確認します。Pin が関係を自動構築するわけではなく、初期化・投影・破棄にも契約が必要です。Unpin 型はこの移動制限から外れます。
図を準備しています 再読み込み
ラッパの移動と固定された値の移動は違う Pin は参照先を固定し、ラッパは移動できます。固定した !Unpin 値は安全な API で移し出せません。
{"id":"rust-pin-pointee","title":"ラッパの移動と固定された値の移動は違う","summary":"Pin は参照先を固定し、ラッパは移動できます。固定した !Unpin 値は安全な API で移し出せません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"handle\" data-i18n=\"handle\"></label><output for=\"handle\" id=\"handle-value\"></output><input id=\"handle\" type=\"range\" min=\"0\" max=\"2\" value=\"0\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"unsafeMove\"><span data-i18n=\"unsafeMove\"></span></label><svg id=\"map\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\" data-i18n-label=\"heading\"></svg><div class=\"metrics\"><div><span data-i18n=\"object\"></span><strong id=\"object\"></strong></div><div><span data-i18n=\"pointer\"></span><strong id=\"pointer\"></strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p></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:550;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:550 20px ui-monospace,monospace}.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:ui-monospace,monospace;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 ui-monospace,monospace;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 ui-monospace,monospace;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 ui-monospace,monospace}.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 ui-monospace,monospace;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:ui-monospace,monospace}.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.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px ui-monospace,monospace;padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 ui-monospace,monospace;overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.chart-wrap{margin:18px 0}.chart-label{font-size:12px;color:var(--muted);min-height:3.2em}.curve{display:block;width:100%;height:auto;max-height:320px;overflow:visible}.curve text{font:11px ui-monospace,monospace;fill:var(--muted)}.gridline{stroke:var(--rule);stroke-width:1}.metrics strong{font-size:20px;min-height:3.2em}.formula{font:12px/1.8 ui-monospace,monospace;min-height:9em}.readout{min-height:8em}.presets button{flex-basis:30%}.scope p{overflow-wrap:anywhere} .unit-note{font-size:12px;color:var(--muted);margin:10px 0}\n\n.metrics span{word-break:break-all;line-break:strict}.metrics>div{padding-right:6px}\n\n.cell{min-height:72px}.metrics strong{min-height:3.6em}\n\n.diagram text{font-size:14px}","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).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';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 band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function draw(){let h=n('handle'),bad=$('#unsafeMove').checked;set('handle-value',h);const g=$('#map'),x=bad?196:8,hx=8+h*110;\ng.setAttribute('viewBox','0 0 360 240');\ng.innerHTML=`<defs><marker id=\"pin-arrow\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto\"><path d=\"M0 0L10 5L0 10Z\" fill=\"var(--accent)\"/></marker></defs>\n<rect class=\"node on\" x=\"${hx}\" y=\"8\" width=\"124\" height=\"38\" rx=\"8\"/><text x=\"${hx+62}\" y=\"32\" text-anchor=\"middle\">Pin<Box<F>></text>\n<path class=\"edge on\" d=\"M${hx+62} 46 L${x+78} 94\" marker-end=\"url(#pin-arrow)\"/>\n${[8,196].map((left,i)=>{const active=left===x,addr=i?'B':'A';return `<g opacity=\"${active?1:.42}\"><rect class=\"node ${active?'gold':''}\" x=\"${left}\" y=\"100\" width=\"156\" height=\"132\" rx=\"8\"/><text x=\"${left+78}\" y=\"124\" text-anchor=\"middle\">${active?'F @ '+addr:addr+' · ∅'}</text><rect class=\"node ${active?'on':''}\" x=\"${left+12}\" y=\"140\" width=\"132\" height=\"30\" rx=\"4\"/><text x=\"${left+78}\" y=\"160\" text-anchor=\"middle\">${addr}.field</text>${active?`<rect class=\"node on\" x=\"${left+12}\" y=\"186\" width=\"132\" height=\"30\" rx=\"4\"/><text x=\"${left+78}\" y=\"206\" text-anchor=\"middle\">ptr = A.field</text>`:''}</g>`}).join('')}\n<path class=\"edge on\" d=\"${bad?'M208 200 H180 V155 H155':'M20 200 H12 V155 H23'}\" marker-end=\"url(#pin-arrow)\" ${bad?'stroke-dasharray=\"5 3\"':''}/>`;set('object',bad?'B':'A');set('pointer','A.field');set('explanation',t(bad?'bad':'ok'));document.body.dataset.pointee=bad?'B':'A';}inputs(draw);","audio":false,"strings":{"bad":"契約に反して F を B に動かすと、内部アドレスは古い A のままです。Pin の安全な API はこの移動を防ぎます。","handle":"ハンドルの位置","heading":"ラッパの移動と固定された値の移動は違う","intro":"Pin<Box<F>> のハンドルを動かし、Future 内部の自己参照を追います。","object":"オブジェクトの位置","ok":"ハンドルが移動してもヒープの F は A のままで、内部ポインタも A を参照します。","pointer":"内部参照の参照先","reset":"リセット","scope":"正しく初期化したアドレス依存の !Unpin の例です。A/B は模式的な位置で、全 Future が自己参照するわけではありません。","scopeLabel":"モデルの範囲と前提","unsafeMove":"不正な移動の結果を表示"}}
await、spawn、join はそれぞれ何をするのか
await は現在のタスク内で子 Future を進行させます。spawn は Future を独立したタスクとして登録します。join 系コンビネータは、OS スレッドを作成せずに、同じタスク内で複数の Future を進行させることができます。並行性はライフサイクルの重複を意味し、並列実行かどうかは実行環境に依存します。
1回の poll で長時間の CPU ループやブロッキング呼び出しを行うと、現在の Executor スレッドを占有してしまいます。関数を単に async とマークしただけでは、其中的な std::thread::sleep、ブロッキング I/O、または重い計算が自動的に実行権を手放すことはありません。
Drop はキャンセルメカニズムの一部
未完成の Future をドロップすることは、Rust の async における一般的なキャンセル方法です。言語はこの動作を許可しており、Future が「自由にドロップできない」と言うべきではありません。これにより保持されている状態がデストラクトされ、そのオブジェクトが後続でポーリングされることはなくなります。ただし、独立したタスク、デバイスに渡されたリクエスト、およびリモートトランザクションは継続する可能性があります。
キャンセル安全性の議論は、ドロップ後に進捗が失われたり、ビジネスプロトコルが破綻したりしないかどうかに関わります。例えば、read_exact が一部のバイトを読み取った後にキャンセルした場合、操作を再作成しても元の位置から始まらない可能性があります。リソースが解放されるかどうかと、ビジネス上の効果が取り消されるかどうかは別の問題です。
構築済み、poll 前 捕捉を保持、本体は未実行 poll 済み、Pending を返す 局所値と子 Future を保持 Ready を返す 結果は呼び出し側に渡済み 捕捉した入力を破棄 未開始の関数本体は実行されない 保存状態を破棄 await の後の文は実行されない 呼び出し側が結果を所有 完了後は再 poll しない キャンセルが発生する段階によって、デストラクトする必要がある状態が決まる これらはオブジェクトのライフサイクルルールです。外部リクエストや独立したタスクが停止するかどうかは、それら独 自のプロトコルに依存します。
キャンセルがローカル値をデストラクトすること、かつ一時停止点後のクリーンアップを実行しないことを検証
use std:: { cell:: Cell, future:: Future, rc:: Rc, task:: { Context, Waker} } ;
struct Resource ( Rc< Cell< u32 > > ) ;
impl Drop for Resource {
fn drop ( & mut self ) { self .0. set ( self .0. get ( ) + 1 ) ; }
}
async fn waiting ( drops : Rc< Cell< u32 > > , after : Rc< Cell< bool > > ) {
let resource = Resource( drops) ;
std:: future:: pending:: < ( ) > ( ) .await;
after.set ( true ) ;
drop ( resource) ;
}
fn main ( ) {
let drops = Rc:: new( Cell:: new( 0 ) ) ;
let after = Rc:: new( Cell:: new( false ) ) ;
let mut future = Box :: pin( waiting ( Rc:: clone( & drops) , Rc:: clone( & after) ) ) ;
let mut context = Context:: from_waker( Waker:: noop( ) ) ;
assert! ( future.as_mut ( ) .poll ( & mut context) .is_pending ( ) ) ;
assert_eq! ( drops.get ( ) , 0 ) ;
drop ( future) ;
assert_eq! ( drops.get ( ) , 1 ) ;
assert! ( ! after.get ( ) ) ;
}
pending は決して自ら Ready になることはありません。ここでは意図的に1回だけ poll してからキャンセルするため、ウェイクアップを待つ必要はありません。Resource は構築され、一時停止点をまたいで生存しているため、キャンセル時に Drop が呼び出されます。after.set(true) は await の後にあり、実行されることはありません。重要なクリーンアップ処理を関数の末尾にのみ記述した場合、キャンセルパスがそこに到達することを保証できません。
通常の Drop は同期デストラクションであり、リモートでの解放確認を自動的に await することはありません。プロトコルレベルでのクローズが必要な場合は、明示的に close/shutdown などの非同期ステップを提供し、キャンセル、タイムアウト、またはクラッシュ後の補償方法を決定する必要があります。ローカルのガードがすでに解放されていても、リモートトランザクションが取り消されたことを推論してはいけません。
Pin 参照のドロップと、所有する Future のドロップ
上記の例では Box::pin を使用して、drop(future) がヒープ上の Future を明示的に破棄できるようにしています。もし pin! が Pin<&mut F> を返す場合、この参照ラッパーをドロップしても、それが指す F が即座に破棄されるわけではありません。実際のオブジェクトは、そのスコープのライフサイクルに従って解放されます。キャンセルを推論する際は、まず Future を所有するコンテナを見つけ、借用を「停止する」ことを「デストラクションがすべて実行された」と誤認しないようにしてください。
キャンセル安全性では進捗が誰に保存されているかを確認する
Future 自身が「何バイト送信したか」という進捗を保存している場合、キャンセルによってこの進捗が失われ、基盤となるストリームがすでに一部を受信している可能性があります。最初から再送信するとデータが重複する可能性があります。進捗が外側の永続オブジェクトに保存されている場合、次の操作で継続できる可能性がありますが、メッセージの境界、冪等性、および障害復旧を定義する必要があります。
したがって、同時に以下の質問を行う必要があります。どのオブジェクトをドロップするか、そのオブジェクトがどの進捗を所有しているか、外部アクションがどこまで進行したか、そして操作を再作成した場合、どこから始まるか。キャンセル安全性は具体的な操作の契約であり、すべての async fn が自動的に備える性質ではありません。
タイムアウト、select、およびバックグラウンドタスクの具体的な境界については、Tokio ランタイム および Tower を参照してください。