借用让函数暂时访问值,而不接管其资源责任。&T 提供共享访问,&mut T 提供独占访问;引用还必须在使用期间指向有效、对齐且满足类型要求的数据。访问权限、有效期与地址条件缺一不可。
“多个读者或一个写者”是普通数据的直觉模型。内部可变性允许共享引用下按另一套协议修改,独占引用也可以被短暂重借用,因此不能把规则理解成所有语法上的引用变量绝不能共存。
Vec 元素借用为什么会阻止 push
push 需要独占访问 Vec,而且容量不足时可能重新分配,使旧元素地址失效。即使已预留容量,普通 push 接口仍要求 &mut Vec,不能在之后还要使用元素引用时绕过借用规则。
借用元素 first = &values[0] 后续使用 first 时,元素必须保持可访问 1 最后一次读取 first 借用的约束可在此后结束 2 随后修改 values 此时可以取得不冲突的独占借用 3 借用约束按后续使用推导 若把 first 的使用移到修改之后,借用范围也随之改变,程序可能被拒绝。
fn main ( ) {
let mut values = vec! [ 1 , 2 , 3 ] ;
let first = & values[ 0 ] ;
assert_eq! ( * first, 1 ) ;
values.push ( 4 ) ;
assert_eq! ( values, [ 1 , 2 , 3 , 4 ] ) ;
}
把 assert_eq!(*first, 1) 移到 push 后面,会让 first 的借用跨过修改,从而产生编译错误。只写一个此后再未使用的引用绑定,则不能据此断言后面的修改一定非法。
NLL(非词法生命周期)按控制流与使用约束推导借用所需区域,不必保守地持续到词法块末尾。它不是简单文本扫描的“最后一行”,分支、返回引用与析构约束都可能影响结果。借用检查概述
把使用位置移过修改点
保持数据和 push 接口不变,只改变 first 的使用位置。橙色区间表示修改所需的独占访问,青色区间表示元素引用仍被后续操作需要。位置相交时,修正方向是改变使用时序或接口,而不是只增加容量。
正在呈现知识画面 重新加载
把使用位置移过修改点 在这个直线程序中,push 后仍读取元素引用会产生借用冲突,即使预留容量也一样。
{"id":"rust-borrow-region","title":"把使用位置移过修改点","summary":"在这个直线程序中,push 后仍读取元素引用会产生借用冲突,即使预留容量也一样。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"use\" data-i18n=\"use\"></label><output for=\"use\" id=\"use-value\"></output><input id=\"use\" type=\"range\" min=\"2\" max=\"6\" value=\"3\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"capacity\"><span data-i18n=\"capacity\"></span></label><div id=\"regions\" class=\"intervals\"></div><pre id=\"code\" class=\"source\"></pre><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.source{min-height:13em}","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 u=n('use');set('use-value',u);intervals('regions',[{start:1,end:u,label:t('borrow')},{start:4,end:4.4,label:t('change'),cls:'gold'}],7);let lines=['let mut v = vec![1, 2, 3];','let first = &v[0];',...Array(5).fill('// …')];lines[4]='v.push(4);';lines[u]+=' use_value(*first);';$('#code').innerHTML=lines.map((s,i)=>'<span class=\"code-line '+(i===u||i===4?'on':'')+'\">'+i+' '+esc(s)+'</span>').join('');set('explanation',t(u>=4?'bad':'ok'));document.body.dataset.allowed=String(u<4);}inputs(draw);","audio":false,"strings":{"bad":"拒绝:push 需要独占 Vec,但之后还要用 first。容量充足不会缩小 push 的签名要求。","borrow":"元素共享借用","capacity":"已经预留足够容量","change":"push 要求 &mut Vec","heading":"把使用位置移过修改点","intro":"拖动引用的最后一次使用,观察 push 与借用区间是否相交。","ok":"允许:最后使用在 push 之前,修改时已不再需要元素借用。","reset":"重置","scope":"只模拟所示普通 Vec::push 接口的直线控制流;NLL 对分支、循环和析构有更完整的分析。","scopeLabel":"模型范围与约定","use":"最后读取 first 的位置"}}
重借用:临时从已有权限派生更短权限
fn append ( values : & mut Vec< i32 > ) { values.push ( 2 ) ; }
fn main ( ) {
let mut values = vec! [ 1 ] ;
let parent = & mut values;
{
let child = & mut * parent;
append ( child) ;
}
parent.push ( 3 ) ;
assert_eq! ( parent, & [ 1 , 2 , 3 ] ) ;
}
parent 是已有的 &mut Vec,child 才是对其目标的重借用。在 child 被使用的期间,不能通过 parent 做冲突访问;child 的借用结束后,可以继续使用 parent。原 Vec 的所有权从未交出去,因此不是“函数返回后所有权恢复”。
已知参数类型为 &mut T 的调用常可隐式重借用,不需要每次显式写 &mut *parent。普通赋值或泛型按值消费不应一概认为都会重借用;&mut T 本身不是 Copy。
显式重借用与移动引用变量
fn consume < T> ( _ : T) { }
fn main ( ) {
let mut number = 1 ;
let parent = & mut number;
consume ( & mut * parent) ; * parent + = 1 ;
assert_eq! ( * parent, 2 ) ;
}
如果把 consume(&mut *parent) 改为 consume(parent),泛型参数 T 会取为 &mut i32,传入的引用值本身被移动,后续 parent 不能再使用。两种写法都没有移动 number 的拥有权,区别是“哪一个引用值被消费”。修复时应让接口接收实际所需的 &mut T,或显式建立短借用,而不是为独占引用寻找 Clone。
独占借用可以拆成不相交部分
fn main ( ) {
let mut values = [ 1 , 2 , 3 , 4 ] ;
let ( left, right) = values.split_at_mut ( 2 ) ;
left[ 0 ] = 10 ;
right[ 0 ] = 30 ;
assert_eq! ( values, [ 10 , 2 , 30 , 4 ] ) ;
}
两个 &mut 切片同时存在是安全的,因为元素范围不重叠。编译器也能识别一些结构体字段的独立借用;对一般的动态索引,则常需要 split_at_mut 等经过证明的接口,而不是期待它推导任意算术不相等条件。
动态选择两个元素,先把不相交关系变成切片边界
fn pair_mut < T> ( values : & mut [T], i : usize , j : usize ) -> Option< ( & mut T, & mut T) > {
if i = = j | | i > = values.len ( ) | | j > = values.len ( ) { return None ; }
if i < j {
let ( left, right) = values.split_at_mut ( j) ;
Some ( ( & mut left[ i] , & mut right[ 0 ] ) )
} else {
let ( left, right) = values.split_at_mut ( i) ;
Some ( ( & mut right[ 0 ] , & mut left[ j] ) )
}
}
fn main ( ) {
let mut values = [ 10 , 20 , 30 ] ;
let ( a, b) = pair_mut ( & mut values, 2 , 0 ) .unwrap ( ) ;
* a + = 1 ;
* b + = 2 ;
assert_eq! ( values, [ 12 , 20 , 31 ] ) ;
assert! ( pair_mut ( & mut values, 1 , 1 ) .is_none ( ) ) ;
assert! ( pair_mut ( & mut values, 0 , 3 ) .is_none ( ) ) ;
assert! ( pair_mut:: < u8 > ( & mut [ ] , 0 , 0 ) .is_none ( ) ) ;
}
i < j 时在 j 处分开,i 一定位于左半,j 对应右半第零项;反向时交换返回顺序,使调用者始终先得到 i 再得到 j。这样既保留输入顺序,又通过安全切片 API 表达了互斥范围。业务只想交换两个位置时直接用 slice::swap 更简单,不必先取得两个引用。拆分借用
方法签名过宽也会制造不必要的冲突
struct Report { title : String, rows : Vec< usize > }
fn append_row ( rows : & mut Vec< usize > , title : & str ) {
rows.push ( title.len ( ) ) ;
}
fn main ( ) {
let mut report = Report { title: String :: from( " demo" ) , rows: Vec :: new( ) } ;
let title = & report.title;
append_row ( & mut report.rows, title) ;
assert_eq! ( report.rows, [ 4 ] ) ;
}
这里 title 和 rows 是不同字段,可以同时共享读取一个、独占修改另一个。如果改成接收 &mut Report 的方法,调用边界就声明它可能独占整个对象,调用者无法仅凭方法内部当前只修改 rows 来缩小这个要求。优先缩窄辅助函数参数,往往比增加 RefCell 或复制 title 更准确地表达职责。
引用变量不需要 mut,目标也可能被修改
let reference = &mut value; 的 reference 绑定没有 mut,仍可通过它修改目标。let mut reference 则额外允许把这个变量重新绑定到另一个引用。区分“指针变量可改”和“拥有独占目标访问权”有助于理解签名。
借用已结束不意味着 guard 已析构。RefCell 的 Ref、MutexGuard 等拥有运行时状态,通常需要在适当作用域退出或显式 drop 才释放相应协议状态,不能直接套用普通引用的 NLL 直觉。
返回引用与省略规则
fn first_word ( text : & str ) -> & str {
& text[ .. text.find ( ' ' ) .unwrap_or ( text.len ( ) ) ]
}
fn main ( ) {
let text = String :: from( " 中文 rust" ) ;
assert_eq! ( first_word ( & text) , " 中文" ) ;
}
函数返回输入中的子切片,不返回局部新建 String 的借用。find 找到空格的字节位置正好处于 UTF-8 边界;不能任意把字符序号当字节偏移。
这里只有一个输入引用生命周期,输出按规则与它关联,不需要显式标注。多个输入存在歧义时才要表达更清楚的关系,见 生命周期 。
String:"中文 rust" 拥有文本与字节缓冲 first_word(&text) 返回同一缓冲中的 &str 使用 word 来源必须保持合法可读 如果后面还要用 word,此前不能通过 text.push 修改来源 返回借用视图:消费视图结束后才能独占修改来源 若确实需要独立文本,显式 to_owned 创建 String;它改变拥有关系,也可能分配。
借用返回值会将约束带回调用方
fn first_word ( text : & str ) -> & str {
& text[ .. text.find ( ' ' ) .unwrap_or ( text.len ( ) ) ]
}
fn main ( ) {
let mut text = String :: from( " 中文 rust" ) ;
let word = first_word ( & text) ;
assert_eq! ( word, " 中文" ) ;
text.push ( ' !' ) ;
let owned = first_word ( & text) .to_owned ( ) ;
text.clear ( ) ;
assert_eq! ( owned, " 中文" ) ;
}
&str 版本让结果与来源关联,适合短期读取;String 版本明确分离存储,适合需要跨越来源修改或释放的使用。后者不是“给引用延长生命周期”,而是创建了另一个拥有者负责的文本。
遇到借用错误时依次检查
问题 对应处理
引用是否仍在修改之后使用 将只读消费移到修改之前,或改变结果表示
两处操作是否实际针对不同字段/范围 缩窄函数参数,使用拆分接口
是否移动了 &mut 引用本身 使用明确参数类型或显式重借用
是否需要长期保存独立数据 返回拥有值,明确复制或共享成本
是否其实是 guard 持有运行时锁 限定 guard 作用域或显式 drop
这些处理分别改变访问时序、访问范围或拥有关系。使用 unsafe 绕过报错不会自动使关系成立,增加 clone 也不保证资源独立,必须先确定真正需要的契约。
编译期检查的保证范围
普通借用检查不在运行时追踪引用计数,也不提供任意资源的业务锁。&[T] 和 &dyn Trait 又包含相应元数据,不能统一说所有引用都固定 8 字节。进入 unsafe、FFI 或内部可变性封装后,额外契约由实现者维护;安全调用方依赖这些封装是 sound 的。