Calling an async fn constructs a Future, which is advanced when polled. A Future describes a computation that has not yet completed; it does not come with its own independent thread. The executor decides when to poll, and asynchronous I/O or other wait sources notify the executor when they can make progress.
An .await does not necessarily imply a single thread switch. If a sub-Future is immediately Ready, the current poll can continue directly, potentially completing multiple awaits within a single poll.
Constructing a Future and Executing the Function Body
usestd::{cell::Cell,future::Future,task::{Context, Poll, Waker}};
async fncalculate(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
}fnmain(){let started =Cell::new(0);let future =calculate(&started);assert_eq!(started.get(),0);letmut future =std::pin::pin!(future);letmut context =Context::from_waker(Waker::noop());assert_eq!(future.as_mut().poll(&mut context),Poll::Ready(42));assert_eq!(started.get(),1);}
The function body runs only on the first poll. Since both ready Futures complete immediately, the entire function returns 42 within a single poll. A noop Waker is used here because the example does not require waiting; it does not schedule tasks to run and cannot drive real asynchronous I/O. Waker::noop
Arguments to a call expression are still evaluated when the Future is constructed. For example, in calculate(make_counter()), the call to make_counter is not automatically deferred; only the execution of the async function body is driven by polling. async move also takes ownership of captured values at the creation site, even if the Future is never polled.
What State the Compiler Saves
The compiler represents an async function as an anonymous state machine capable of suspending and resuming. It saves the currently awaited sub-Futures and local values needed for later. When waiting for read_file, it typically saves the state of the read operation, not the file content that has been received but not yet yielded.
The specific layout of the state machine is an implementation detail; you should not treat "each await must correspond to an enum variant with the exact same name" as an ABI guarantee. Large values crossing suspension points can increase the size of the Future; ending their lifetime early can sometimes reduce state size.
The Contract Between poll and Waker
Future::poll takes Pin<&mut Self> and Context, returning Poll<Output>. Pending means there is no result yet; the implementation must establish a wake path for when the task can make progress. This work can be delegated to the polled sub-Futures, avoiding the need to save the Waker at every layer.
wake is a scheduling hint; it can be coalesced or occur before poll returns Pending. When implementing your own waiting primitives, you must correctly handle the race between checking state and registering notifications. You cannot simply "check for no result, then register a callback," as this might miss a completion event that happens in between.
Once Ready is returned, the caller should not arbitrarily poll the completed Future again. Specific types may panic or exhibit other behavior defined by their interface; do not treat it as a protocol like Iterator where next can be called again. Future
Ready Means Complete, Not Necessarily Successful
A Future's Output can be Result<T, E>. Thus, Poll::Ready(Err(error)) means the computation has completed and returned an error; Pending means the final result has not yet been obtained. The executor should not treat Err as "not ready yet" and continue polling.
poll Result
Scheduler Meaning
Caller Handling
Pending
Cannot complete currently
Wait for notification, then advance again
Ready(Ok(value))
Completed
Use the business result
Ready(Err(error))
Completed
Propagate, report, or recreate the operation based on business logic
The same Future may receive different task Wakers on subsequent polls, for example, when driven by different outer combinators. Waiting primitives must update the current notification target according to the protocol; you cannot assume the first registration remains valid forever. wake itself carries no result; results must be stored in correctly synchronized shared state and read by poll. Future::poll
A waiting primitive that is easy to reason about uses a single lock to protect both "whether the result exists" and "the current Waker": the consumer checks the result inside the lock, and if incomplete, registers the Waker; the producer writes the result and retrieves the Waker inside the lock, then calls wake after unlocking. This avoids the scenario where a completion event falls exactly between the check and the registration, leaving no one to notify. Real-world libraries also handle shutdown, duplicate completions, cancellation, and fairness; do not treat this sequence as a complete channel implementation.
Waking is not executing
Separate external readiness, waking and polling to avoid imagining await as an independently running function. Poll to Pending, then trigger I/O alone: the task becomes schedulable, but the Future has not returned a result. Repeated wakes do not require an equal number of polls.
Preparing the visual
Waking is not executing
Only polling advances a Future; wake requests later polling, and Pending is not autonomous background execution.
The following Future requests rescheduling on the first poll and returns a result on the second. The example uses the safe Wake interface and does not require writing a RawWaker:
This is a deterministic two-poll demonstration, not a general executor. A real executor must maintain task queues and handle notification races; you cannot simply map every Pending to an unconditional park.
Pin Stabilizes the Pointed-to Value
The state machine may contain references to its own internal data. Therefore, once address stability is established via the appropriate pinning contract, you cannot arbitrarily move a pinned !Unpin value until it is properly destructed.
Pin is not a magical address lock applied by the compiler to all values; it is a contract maintained by restricted operations in safe APIs and obligations in unsafe implementations. The pointer wrapper Pin<Box<F>> itself can be moved, as long as the F it points to remains in place; when F: Unpin, Pin does not impose the same movement restrictions. Pin
Before the first pinning, a Future can usually be moved. Not every async Future is actually self-referential, nor does having a String local variable necessarily mean moving the String object moves its heap buffer; you must distinguish between the object and the storage it points to.
Moving the wrapper is not moving the pinned value
First move only the owning handle: address-sensitive F stays at heap location A. Then inspect the counterfactual move of F itself and why its internal address does not update automatically. Pin does not establish those relationships; initialization, projection and destruction must uphold the contract. Unpin types can opt out of this movement restriction.
Preparing the visual
Moving the wrapper is not moving the pinned value
Pin pins the pointee; the wrapper can move. Safe APIs do not move out a pinned !Unpin value.
{"id":"rust-pin-pointee","title":"Moving the wrapper is not moving the pinned value","summary":"Pin pins the pointee; the wrapper can move. Safe APIs do not move out a pinned !Unpin value.","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":"If F is moved to B against the contract, its internal address still points to old A. Pin’s safe APIs prevent this move.","handle":"Handle location","heading":"Moving the wrapper is not moving the pinned value","intro":"Move a Pin<Box<F>> handle and track a Future’s internal self-reference.","object":"Object address","ok":"The handle moved, but heap F remains at A and its internal pointer still targets A.","pointer":"Internal reference target","reset":"Reset","scope":"An already correctly initialized address-sensitive !Unpin object; A/B are schematic addresses. Not all Futures are self-referential.","scopeLabel":"Model scope and assumptions","unsafeMove":"Show an invalid move’s consequence"}}
What await, spawn, and join Do
await advances a sub-Future within the current task; spawn registers a Future as an independent task; join-like combinators can advance multiple Futures within the same task without creating OS threads. Concurrency is about overlapping lifetimes; whether it is parallel depends on the execution environment.
If a poll executes a long CPU loop or a blocking call, it will occupy the current executor thread. Merely marking a function as async does not automatically yield execution for std::thread::sleep, blocking I/O, or heavy computation.
Drop Is Part of the Cancellation Mechanism
Dropping an incomplete Future is a common cancellation method in Rust async; the language allows this, and you should not say a Future "cannot be dropped arbitrarily." It destructs the state it holds and stops the object from being polled further; however, independent tasks, requests handed off to devices, and remote transactions may continue.
Cancellation safety discusses whether progress is lost or business protocols are broken upon dropping. For example, if read_exact has already read some bytes and is cancelled, recreating the operation may not resume from the original position. Whether resources are released and whether business effects are rolled back are two separate issues.
Verifying Cancellation Destructs Local Values, But Does Not Execute Post-Suspension Cleanup
pending never becomes ready on its own; here we intentionally poll only once before cancelling, so no wake is needed. Resource is constructed and survives across the suspension point, so Drop is called upon cancellation; after.set(true) is after the await and is never executed. If critical cleanup is written only at the end of the function, you cannot guarantee the cancellation path will reach it.
Standard Drop is synchronous and does not automatically await remote release confirmation. When protocol-level shutdown is needed, you should explicitly provide asynchronous steps like close/shutdown, and decide on compensation for cancellation, timeouts, or crashes. Even if a local guard is released, you cannot infer that a remote transaction has been rolled back.
Dropping a Pin Reference vs. Dropping an Owned Future
The example above uses Box::pin so that drop(future) explicitly destroys the heap-allocated Future. If pin! yields a Pin<&mut F>, dropping this reference wrapper does not immediately destroy the F it points to; the actual object is still released according to its scope's lifetime. When reasoning about cancellation, first find the container that owns the Future to avoid mistaking "stopping holding a borrow" for "having executed all destructors."
Cancellation Safety Requires Checking Who Holds the Progress
If the Future itself saves "how many bytes have been sent," cancellation will lose this progress, while the underlying stream may have already received part of it; re-sending from the beginning might duplicate data. If progress is saved in an outer persistent object, the next operation has a chance to continue, but message boundaries, idempotency, and fault recovery must still be defined.
Therefore, you should simultaneously ask: which object is being dropped, what progress does it own, where have external actions progressed, and where would recreating the operation start? Cancellation safety is a contract for specific operations, not a property automatically possessed by all async fns.
For specific boundaries regarding timeouts, select, and background tasks, continue reading the Tokio Runtime and Tower.