Interior mutability allows objects accessible via shared references to modify part of their internal state, such as caches, counters, and synchronization queues. It does not negate the requirement that "conflicting accesses cannot coexist"; rather, the encapsulation chooses a different access protocol that can be proven safe.
The general immutability constraint of shared references has exceptions for parts wrapped in UnsafeCell. However, UnsafeCell itself does not provide locking, does not allow arbitrarily creating mutually aliased &muts, and does not allow data races. UnsafeCell
Cell is Not Exclusive to Copy Types
Cell::get requires T: Copy because it returns a copy of the internal value. set and replace can be used with non-Copy types; replace moves the old value out and puts a new one in, without exposing internal borrows. take replaces the value with a default one when T: Default, and into_inner consumes the Cell and retrieves the T. Cell
usestd::cell::Cell;fnmain(){let name =Cell::new(String::from("before"));let old = name.replace(String::from("after"));assert_eq!(old,"before");assert_eq!(name.take(),"after");assert_eq!(name.into_inner(),"");}
Thus, Cell<String> is valid and useful, even though you cannot call get. It lacks the dynamic borrow counting of RefCell, and it is not equivalent to operations being absolutely zero-cost: replacing a String may still involve resource management.
Saying "Cell never returns references" is too absolute. When holding &mut Cell<T>, get_mut can return &mut T; in this case, exclusive borrowing is already guaranteed by the compiler, requiring no additional runtime protocol.
Single-threaded code must also consider observing intermediate states
Temporarily taking out a Vec using Cell::take, processing it, and then setting it back seems to avoid borrow guards; but if reentrant external code is called during processing, other entry points might see the default empty Vec. If a panic occurs during processing, the taken value might be dropped directly, leaving the original Cell empty. Cell guarantees that such access is memory-safe, but does not guarantee that "take—process—put back" has transactional semantics.
Therefore, Cell is better suited for data that inherently allows whole-value replacement and where intermediate states have clear meaning. If you must temporarily borrow internal structures, RefCell can more directly express exclusivity during access; if operations might fail, you should calculate the new value before committing, or design recoverable state transitions.
RefCell Records Dynamic Borrows via Guards
borrow returns Ref<T>, and borrow_mut returns RefMut<T>. Generally, multiple shared borrows are allowed, or one exclusive borrow. The borrow only ends when the guard is dropped; you cannot assume the guard automatically becomes invalid just because "the last read value has passed."
borrow/borrow_mut panic on conflict; try_borrow/try_borrow_mut return the conflict as a Result and do not wait for another borrow to end. Commonly overlooked sources of conflict include callback reentrancy and temporary guards that live across the entire match or other long expressions. RefCell
RefCell<T> can be moved to another thread as a whole if T: Send, but it is not Sync. Rc<RefCell<T>> is single-threaded shared ownership plus dynamic borrowing, not a combination that can be shared across threads.
When returning references to internal fields, return the guard together
You cannot create a temporary Ref in a function and then return the &str inside it as a normal reference without a guard; the dynamic borrow credential would disappear when the function ends. Ref::map projects the visibility scope of the guard to the field while preserving the shared borrow state of the entire RefCell. Even if you only read name, you cannot simultaneously borrow the exclusive numeric field through the same RefCell; mapping does not automatically split one RefCell into two independent borrowing units. Ref::map
Here, the required scalar is extracted first, the guard is released, and then the callback is called, so the callback can modify the queue. If borrow_mut is called while reading is still alive, a borrow conflict will occur. The cost is that count only represents the state at the time of retrieval and is no longer guaranteed to equal the queue's current length after the callback executes; if business logic requires stable state, you should restrict what the callback can do, or change it to submitting events for outer-layer processing.
Do not rely on "single-threaded means no contention" to ignore reentrancy. Single-threaded code does not execute two machine instruction streams simultaneously, but external callbacks can re-enter the same object before the original operation is complete.
Does the guard remain after the last read?
Reading and dropping a guard are deliberately separate actions. Obtain a Ref, read once, then request RefMut: it still fails. Exclusive borrowing becomes possible only after every shared guard is released. This is why ordinary-reference last-use reasoning does not directly release Ref or MutexGuard.
Preparing the visual
Does the guard remain after the last read?
Dynamic borrowing follows guard lifetime, not the last read.
{"id":"rust-refcell-guards","title":"Does the guard remain after the last read?","summary":"Dynamic borrowing follows guard lifetime, not the last read.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"read\" data-i18n=\"read\"></button><button id=\"write\" data-i18n=\"write\"></button><button id=\"last\" data-i18n=\"last\"></button><button id=\"dropRead\" data-i18n=\"dropRead\"></button><button id=\"dropWrite\" data-i18n=\"dropWrite\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"guards\"></h3><div id=\"guards\"></div></section><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"initial\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"used\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"dropped\"></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#guards{min-height:110px}#state{min-height:3em;font:16px ui-monospace,monospace}.track{flex-wrap:wrap}.cell{flex-basis:20%}","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 r=0,w=false,msg='initial';function draw(){cells('guards',[...Array.from({length:r},(_,i)=>({text:'Ref '+i,cls:'on'})),...(w?[{text:'RefMut',cls:'gold'}]:[])]);set('state',w?'exclusive':r?'shared × '+r:'unborrowed');$('#read').disabled=r>=4;$('#last').disabled=!r&&!w;$('#dropRead').disabled=!r;$('#dropWrite').disabled=!w;set('explanation',t(msg));document.body.dataset.readers=r;document.body.dataset.writer=w;}for(let id of ['read','write','last','dropRead','dropWrite','reset'])$('#'+id).onclick=()=>{msg='ok';if(id==='read'){if(w)msg='bad';else r++}if(id==='write'){if(w||r)msg='bad';else w=true}if(id==='last')msg='used';if(id==='dropRead'){r--;msg='dropped'}if(id==='dropWrite'){w=false;msg='dropped'}if(id==='reset'){r=0;w=false;msg='initial'}draw()};draw();","audio":false,"strings":{"bad":"Err: a conflicting guard exists; try_ methods do not wait.","dropRead":"Drop one Ref","dropWrite":"Drop RefMut","dropped":"The guard was dropped, ending its dynamic borrow.","guards":"Live guards","heading":"Does the guard remain after the last read?","initial":"No guards; either shared or exclusive access may be requested.","intro":"Request shared or exclusive borrows, then explicitly drop guards.","last":"Read once","ok":"Success: created a guard that must be released.","read":"try_borrow()","reset":"Reset","scope":"Models unsplit guards using try_borrow/try_borrow_mut; conflicts return errors without waiting or running Rust.","scopeLabel":"Model scope and assumptions","state":"Borrow state","used":"Reading does not drop the guard; another exclusive borrow may still fail.","write":"try_borrow_mut()"}}
Mutex Extends Exclusive Access Across Threads
std::sync::Mutex::lock waits to acquire the lock and returns LockResult<MutexGuard<T>>. The guard's release unlocks; the lock protects data access following the same protocol and cannot allow some code to bypass the lock while expecting consistency.
try_lock does not block and returns a TryLockResult, which is a Result where errors distinguish between WouldBlock and Poisoned. The following verifies the contention branch while another thread holds the lock, avoiding undefined behavior from recursive locking in the same thread:
Poisoning reminds callers that a lock-holding thread previously left the critical section during panic unwinding, and business state may be incomplete. It is a suggestive check, not a complete safety proof; when recovering, you must evaluate invariants, cannot ignore them unconditionally, and cannot let unsafe code rely solely on poisoning to avoid UB. Mutex
The underlying implementation depends on the platform and standard library version, so you cannot definitively say it is always pthread_mutex_t on Linux. Choosing it relies primarily on its mutual exclusion contract, rather than internal fields of a specific implementation.
Locks Protect Invariants, Not Just Variables
The following uses one lock to protect two account balances. Transfers must maintain the total, and retain the original state if the balance is insufficient or the target overflows. Calculating all new values first, then committing, avoids the scenario where the first field is modified but the second field's validation fails.
usestd::sync::Mutex;#[derive(Debug, PartialEq, Eq)]enumTransferError{ Insufficient, Overflow }fntransfer(balances:&Mutex<(u64, u64)>, amount:u64)->Result<(), TransferError>{letmut state = balances.lock().unwrap();let left = state.0.checked_sub(amount).ok_or(TransferError::Insufficient)?;let right = state.1.checked_add(amount).ok_or(TransferError::Overflow)?;*state =(left, right);Ok(())}fnmain(){let balances =Mutex::new((100,20));assert_eq!(transfer(&balances,30),Ok(()));assert_eq!(*balances.lock().unwrap(),(70,50));assert_eq!(transfer(&balances,80),Err(TransferError::Insufficient));assert_eq!(*balances.lock().unwrap(),(70,50));let full =Mutex::new((10,u64::MAX));assert_eq!(transfer(&full,1),Err(TransferError::Overflow));assert_eq!(*full.lock().unwrap(),(10,u64::MAX));}
The example treats poisoning as an unrecoverable error and unwraps, which is suitable for demonstrating arithmetic and commit order; actual interfaces can return lock errors separately or define recovery strategies. If each balance has its own lock, unlocking them sequentially exposes intermediate states; holding two locks simultaneously requires a unified locking order to avoid deadlocks. Two atomic integers do not automatically constitute one atomic transfer.
Locks should contain the reads and commits necessary to maintain invariants, but slow I/O, unknown callbacks, or long calculations should be included cautiously. Moving calculations outside the lock shortens lock-holding time, but you must check if the old state the calculation depends on has changed; if necessary, record the version and verify after re-locking, do not assume calculations outside the lock still correspond to the latest data.
Poisoning Does Not Roll Back Already Written Data
PoisonError can return the guard, giving the caller a chance to check and repair the state. Calling into_inner merely retrieves the access credential; clear_poison clears the flag, neither of which automatically restores business invariants. The following constructs a controlled thread panic, then replaces the corrupted state with a known complete state.
usestd::sync::Mutex;usestd::thread;fnmain(){let state =Mutex::new((50u32,50u32));thread::scope(|scope|{let shared =&state;let failed = scope.spawn(move||{letmut guard = shared.lock().unwrap();
guard.0=0;panic!("Simulate updating only one field");});assert!(failed.join().is_err());});assert!(state.is_poisoned());{letmut guard =match state.lock(){Ok(_)=>panic!("Expected poisoned state"),Err(error)=> error.into_inner(),};assert_eq!(*guard,(0,50));*guard =(50,50);// In teaching, known trusted complete state; actual recovery must have trusted basis.
state.clear_poison();}assert!(!state.is_poisoned());assert_eq!(*state.lock().unwrap(),(50,50));}
This example relies on panic unwinding and observing thread failure via join; under panic=abort, the process terminates, and recovery via this flow is impossible. Real-world balances cannot be reset out of thin air; they require trusted snapshots, logs, or business reconciliation. Poisoning detection itself is not a reliability boundary that is necessarily triggered in all panic scenarios. Mutex poisoning
Selection Boundaries for the Three
Question
Cell
RefCell
Mutex
Modify via &self
Supports replacement operations
Via dynamic borrowing
Via lock-holding guard
Access conflicts
Does not maintain standard shared borrows
Returns borrow error or panic
Waits or try_lock returns error
Whole Send when T: Send
Yes
Yes
Yes
Can implement Sync
No
No
Yes when T: Send
When only replacing the whole value, Cell is often simpler; when you need to temporarily borrow internal structures and limit to single-threaded, RefCell is more suitable; when maintaining invariants across multiple fields between threads, Mutex is usually easier to reason about. For asynchronous scenarios, you must also consider whether waiting blocks the executor and whether guards cross await, see Tokio runtime.