这些类型首先决定谁负责让值继续存活、谁最终释放它。Box 表达一个所有者;Rc 和 Arc 表达多个共享所有者。是否需要修改内部值、是否需要跨线程同步,是另一组问题。
类型 所有权 克隆的含义 典型用途
Box 独占 T: Clone 时克隆内部值到新的 Box 递归结构、拥有 DST、独立分配
Rc 共享,非原子计数 增加强引用,不克隆 T 单线程共享结构
Arc 共享,原子计数 增加强引用,不克隆 T 满足线程约束的共享所有权
Weak 不保活 T 增加弱引用 反向链接、非拥有的观察者
Box 把递归尺寸变成间接引用
如果一个节点直接包含下一个同类型节点,其尺寸会递归依赖自身。通过 Box 间接持有下一个节点,当前节点的尺寸就可以确定。Box、Box<[T]> 还能拥有编译期不知道具体尺寸的值。
对于 Sized T,普通 Box 有单指针表示保证,但指针宽度依赖目标平台;DST 指针还携带相应元数据,不能统称为“8 字节”。零大小类型的 Box 不需要为 T 申请非零堆空间。Box
Box::new(large_array) 表达的是最终拥有堆上值,并不保证构造过程中绝无大数组栈临时量。大字节缓冲可从 Vec 构造再 into_boxed_slice,不要用源代码里只见一个指针来推断峰值栈用量。
fn main ( ) {
let bytes: Box< [ u8 ] > = vec! [ 0 ; 1_000_000 ] .into_boxed_slice ( ) ;
assert_eq! ( bytes.len ( ) , 1_000_000 ) ;
let moved = bytes; assert_eq! ( moved[ 0 ] , 0 ) ;
}
Box 本身可以移动,不代表内部值被固定:通过合法的 &mut T 仍可替换或移出 T。需要地址固定契约时还涉及 Pin,见 内存布局 。
移动 Box 与移动 Box 内的值
fn main ( ) {
let boxed = Box :: new( String :: from( " owned" ) ) ;
let address = ( & * boxed) as * const String ;
let moved = boxed;
assert_eq! ( ( & * moved) as * const String , address) ;
let value: String = * moved;
assert_eq! ( value, " owned" ) ;
}
移动 Box 句柄时,示例中的堆上 String 对象仍在原处;之后 *moved 则取得 String 的所有权,消耗 Box。String 所拥有的字节缓冲与堆上 String 对象本身还要分开看。地址在一次句柄移动前后相同,并不证明 T 永远不可移动,更不能替代 Pin 的契约。
递归结构还应考虑销毁路径。Box 解决类型尺寸无限递归,不会自动消除长链逐层 Drop 的调用深度;非常长的链表可能需要显式迭代拆除。若只是连续元素,Vec 往往在布局和遍历上更直接。
Rc 和 Arc 共享的是同一个值
Rc::clone(&a) 与 Arc::clone(&a) 创建新的所有权句柄,通常不要求 T: Clone。值和计数如何排布属于实现细节,不应把某个平台上的字段字节数画成稳定 ABI。
Rc 既不 Send 也不 Sync。Arc 的计数可以跨线程协调,但对于默认分配器的 Arc,要实现 Send 和 Sync 仍要求 T: Send + Sync。Arc<RefCell> 不会自动得到线程安全的内部借用。Arc
Arc 的原子计数成本受架构、竞争和缓存影响,不能给出通用的“比 Rc 慢 10 倍”结论。频繁克隆是否重要,应结合实际热路径测量;借用 &T 足够时,也不必每次创建一个所有者。
值相等与同一个共享对象
use std:: rc:: Rc;
fn main ( ) {
let first = Rc:: new( String :: from( " same" ) ) ;
let shared = Rc:: clone( & first) ;
let independent = Rc:: new( String :: from( " same" ) ) ;
assert_eq! ( first, independent) ; assert! ( Rc:: ptr_eq( & first, & shared) ) ;
assert! ( ! Rc:: ptr_eq( & first, & independent) ) ;
assert_eq! ( Rc:: strong_count( & first) , 2 ) ;
drop ( shared) ;
assert_eq! ( Rc:: strong_count( & first) , 1 ) ;
}
Rc::clone 明确表示复制所有权句柄;(*first).clone() 才调用 String 的 Clone。后者产生另一个 String,但一般类型的 Clone 也未必深拷贝全部可达数据,例如含 Arc 字段的结构可能仍共享该字段。因此“克隆了 T”不等于“得到完全隔离的对象图”。
当函数只在调用期间读取数据时,接收 &T 即可;只有需要保留共享所有权时,接收或克隆 Rc/Arc 才能表达相应契约。把所有参数都改成 Arc 会将分配与共享策略传播到并不需要它的接口。
Weak:不保活值,但能安全尝试获取所有权
至少一个强引用 T 存活;Weak 可以 upgrade 获取强引用 1 最后一个强引用被释放 析构 T;已有 Weak 的 upgrade 返回 None 2 最后一个 Weak 也被释放 引用计数使用的剩余分配可以回收 3 值的生命期与引用计数分配的生命期 普通 Rc/Arc 生命周期示意;Weak 不保活 T,也不等于裸指针。
use std:: cell:: Cell;
use std:: rc:: Rc;
struct Tracked ( Rc< Cell< usize > > ) ;
impl Drop for Tracked {
fn drop ( & mut self ) { self .0. set ( self .0. get ( ) + 1 ) ; }
}
fn main ( ) {
let drops = Rc:: new( Cell:: new( 0 ) ) ;
let first = Rc:: new( Tracked( Rc:: clone( & drops) ) ) ;
let second = Rc:: clone( & first) ;
let observer = Rc:: downgrade( & first) ;
drop ( first) ;
assert! ( observer.upgrade ( ) .is_some ( ) ) ;
assert_eq! ( drops.get ( ) , 0 ) ;
drop ( second) ;
assert_eq! ( drops.get ( ) , 1 ) ;
assert! ( observer.upgrade ( ) .is_none ( ) ) ;
}
upgrade 成功返回的新强引用,会在其存活期间保活 T;失败返回 None,而不是暴露一个可解引用的悬垂指针。最后一个强引用释放时 T 已析构,即使 Weak 仍让其背后的分配保留。
常见树模型是父节点强引用子节点,子节点用 Weak 指向父节点。关键不是固定哪个方向必须弱,而是所有权图中不能让不再需要的节点通过强引用环互相保活。仅有父到子的强引用不会构成环;双向都强引用才可能形成环。
反向链接要允许目标已经消失
use std:: cell:: RefCell;
use std:: rc:: { Rc, Weak} ;
struct Node {
name : & 'static str ,
parent : RefCell< Weak< Node> > ,
children : RefCell< Vec< Rc< Node> > > ,
}
impl Node {
fn new ( name : & 'static str ) -> Rc< Self > {
Rc:: new( Self {
name, parent: RefCell:: new( Weak:: new( ) ) ,
children: RefCell:: new( Vec :: new( ) ) ,
} )
}
}
fn main ( ) {
let child = Node:: new( " child" ) ;
{
let parent = Node:: new( " parent" ) ;
* child.parent.borrow_mut ( ) = Rc:: downgrade( & parent) ;
parent.children.borrow_mut ( ) .push ( Rc:: clone( & child) ) ;
let owner = child.parent.borrow ( ) .upgrade ( ) .unwrap ( ) ;
assert_eq! ( owner.name, " parent" ) ;
}
assert! ( child.parent.borrow ( ) .upgrade ( ) .is_none ( ) ) ;
assert_eq! ( child.name, " child" ) ;
}
child 有外部强所有者,因此 parent 销毁后仍可存活;其父链接却不能再升级。这是 Weak 允许的正常状态,调用者应处理 None。例子只展示一次连接;完整树 API 还需要维护旧父节点列表、禁止祖先成为后代等结构不变量,Weak 本身不会验证这些业务规则。
Weak 也不是自动清理的缓存。若 HashMap 的值都是 Weak,目标消失后键和弱句柄仍可能留在表里;需要在 upgrade 失败时删除或定期清扫。存活期与缓存容量是不同问题。
值销毁与分配释放是两个时刻
先创建 Weak,再释放所有强句柄:T 会先消失,控制分配仍保留给 Weak 使用。随后尝试 upgrade 会得到 None。最后释放 Weak,分配才可回收。反过来,在还有强句柄时 upgrade 成功,会真实增加一个需要管理的强拥有者。
正在呈现知识画面 重新加载
值销毁与分配释放是两个时刻 最后一个强引用消失时 T 被析构;现存 Weak 保留控制分配,但不能复活 T。
{"id":"rust-weak-lifetime","title":"值销毁与分配释放是两个时刻","summary":"最后一个强引用消失时 T 被析构;现存 Weak 保留控制分配,但不能复活 T。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"strongAdd\" data-i18n=\"strongAdd\"></button><button id=\"strongDrop\" data-i18n=\"strongDrop\"></button><button id=\"weakAdd\" data-i18n=\"weakAdd\"></button><button id=\"weakDrop\" data-i18n=\"weakDrop\"></button><button id=\"upgrade\" data-i18n=\"upgrade\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"metrics\"><div><span data-i18n=\"strong\"></span><strong id=\"strong\"></strong></div><div><span data-i18n=\"weak\"></span><strong id=\"weak\"></strong></div></div><div class=\"two\"><section class=\"card\"><header data-i18n=\"value\"></header><div id=\"value\" class=\"cell\"></div></section><section class=\"card\"><header data-i18n=\"allocation\"></header><div id=\"allocation\" class=\"cell\"></div></section></div><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=\"success\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"fail\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"changed\"></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 ","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 s=1,w=0,msg='initial';function draw(){set('strong',s);set('weak',w);set('value',t(s?'alive':'gone'));set('allocation',t(s||w?'kept':'gone'));$('#value').className='cell '+(s?'on':'empty');$('#allocation').className='cell '+(s||w?'gold':'empty');$('#strongAdd').disabled=!s||s>=8;$('#strongDrop').disabled=!s;$('#weakAdd').disabled=!s||w>=8;$('#weakDrop').disabled=!w;$('#upgrade').disabled=!w||s>=8;set('explanation',t(msg));}for(let id of ['strongAdd','strongDrop','weakAdd','weakDrop','upgrade','reset'])$('#'+id).onclick=()=>{msg='changed';if(id==='strongAdd')s++;if(id==='strongDrop')s--;if(id==='weakAdd')w++;if(id==='weakDrop')w--;if(id==='upgrade'){msg=s?'success':'fail';if(s)s++;}if(id==='reset'){s=1;w=0;msg='initial'}draw()};draw();","audio":false,"strings":{"alive":"存活","allocation":"控制分配","changed":"句柄数量已更新;观察值与分配是否仍存在。","fail":"upgrade 返回 None;Weak 无法复活已析构的 T。","gone":"已释放","heading":"值销毁与分配释放是两个时刻","initial":"Rc::new(T):一个强句柄。","intro":"增加或释放强弱句柄,尝试从 Weak 升级。","kept":"保留","reset":"重置","scope":"单线程 Rc 的逻辑计数;weak 数是用户持有的 Weak 数,不包含实现的隐式计数。","scopeLabel":"模型范围与约定","strong":"强引用数","strongAdd":"克隆强句柄","strongDrop":"释放强句柄","success":"upgrade 返回 Some,并新增一个强句柄。","upgrade":"尝试 upgrade","value":"T 的生命","weak":"弱引用数","weakAdd":"创建弱句柄","weakDrop":"释放弱句柄"}}
修改共享值:先明确是否要让其他所有者看见
需求 方式 后果
已经唯一拥有,直接修改 get_mut 不克隆;存在其他强引用或 Weak 时返回 None
修改自己的版本 make_mut 多个强引用时克隆 T,旧所有者仍看见旧值
修改所有者共同看见的状态 RefCell、Mutex 等内部可变性 保持共享对象,另外约束访问
消费最后一个强所有者,取出 T try_unwrap 强引用数为 1 即可,已有 Weak 不阻止成功
对于 Rc/Arc 的 make_mut,只有一个强引用但还有 Weak 时,会让这些 Weak 与新持有的值脱离关联,不需要克隆 T;Weak 后续不能升级到修改后的值。这与 get_mut 直接拒绝的行为不同。Rc::make_mut
use std:: rc:: Rc;
fn main ( ) {
let mut current = Rc:: new( String :: from( " v1" ) ) ;
let snapshot = Rc:: clone( & current) ;
Rc:: make_mut( & mut current) .push_str ( " +edit" ) ;
assert_eq! ( & * snapshot, " v1" ) ;
assert_eq! ( & * current, " v1+edit" ) ;
let weak = Rc:: downgrade( & current) ;
assert! ( Rc:: get_mut( & mut current) .is_none ( ) ) ;
Rc:: make_mut( & mut current) .push ( ' !' ) ;
assert! ( weak.upgrade ( ) .is_none ( ) ) ;
let another_weak = Rc:: downgrade( & current) ;
assert_eq! ( Rc:: try_unwrap( current) .unwrap ( ) , " v1+edit!" ) ;
assert! ( another_weak.upgrade ( ) .is_none ( ) ) ;
}
只有一个强引用 且没有 Weak 存在多个强引用 其他所有者保留旧版本 一个强引用 + Weak 弱观察者不保活 T 直接得到 &mut T 不克隆 T 克隆 T,再修改新版本 原句柄仍指向旧对象 脱离已有 Weak 不克隆 T;旧 Weak 失效 make_mut:按当前拥有关系决定是否复制 这是 make_mut 的行为分支;get_mut 不会替调用者复制或脱离 Weak。
写时克隆未必是深层快照
use std:: cell:: Cell;
use std:: rc:: Rc;
# [ derive ( Clone ) ]
struct Version {
title : String,
counter : Rc< Cell< u32 > > ,
}
fn main ( ) {
let mut current = Rc:: new( Version {
title: String :: from( " v1" ) , counter: Rc:: new( Cell:: new( 0 ) ) ,
} ) ;
let old = Rc:: clone( & current) ;
Rc:: make_mut( & mut current) .title.push_str ( " edited" ) ;
current.counter.set ( 7 ) ;
assert_eq! ( old.title, " v1" ) ;
assert_eq! ( current.title, " v1 edited" ) ;
assert_eq! ( old.counter.get ( ) , 7 ) ;
assert! ( Rc:: ptr_eq( & old.counter, & current.counter) ) ;
assert! ( ! Rc:: ptr_eq( & old, & current) ) ;
}
外层 Version 已分离,但派生 Clone 对 counter 只复制 Rc,两个版本仍看到同一个计数器。如果产品语义要求完整不可变快照,需要保证字段本身不可变,或明确克隆各层数据;仅在外面套一层 Rc 并调用 make_mut 不足以建立该保证。
取回 T 失败时,仍然持有原来的所有权
use std:: rc:: Rc;
fn main ( ) {
let first = Rc:: new( String :: from( " keep me" ) ) ;
let second = Rc:: clone( & first) ;
let first = Rc:: try_unwrap( first) .expect_err ( " 还有另一个强所有者" ) ;
assert! ( Rc:: ptr_eq( & first, & second) ) ;
drop ( second) ;
assert_eq! ( Rc:: try_unwrap( first) .unwrap ( ) , " keep me" ) ;
}
try_unwrap 失败返回 Err 中的原句柄,调用者可以继续保留它,而不是必须丢弃共享对象。Arc 的并发场景还要注意:多个线程分别执行 try_unwrap(...).ok() 可能都没取得 T,随后各自丢弃 Err,最终由析构释放 T。若希望所有强所有者都参与“恰有一个取走值”的协议,可研究 Arc::into_inner 的对应保证,并确保每个强句柄都遵循协议。Arc::into_inner
不要先检查 Arc::strong_count 再用 unsafe 制造 &mut T:计数查询与其他线程操作之间可能发生变化,且还涉及 Weak。使用 get_mut 等封装好的契约。内部可变性的具体选择见 Cell、RefCell 与 Mutex 。