分配器提供满足大小和对齐要求的存储,类型规则决定哪些字节组成有效值,所有权决定何时析构和释放。把这几层分开,才能理解为什么“拿到非空指针”并不等于已经拥有一个合法的 T。
计算 Layout 检查大小、对齐与溢出 1 申请存储并检查失败 成功得到对齐的未初始化内存 2 初始化并使用 T 写入合法值,维护所有权和访问规则 3 结束生命周期并释放存储 需要时析构 T;使用匹配的分配器和 Layout 4 申请字节与创建有效的 T 是不同阶段 普通业务优先使用 Box、Vec 等 RAII 容器,避免手动维护完整路径。
全局分配器与系统分配器
Rust 没有把“所有平台默认调用 glibc malloc”规定为语言契约。System 表示平台系统分配器,在不同系统上使用不同基础设施;默认选择和静态/动态库环境也应以目标文档为准。System
下面的完整程序显式选择 System,不需要为了转发而手写一份 GlobalAlloc:
use std:: alloc:: System;
# [ global_allocator ]
static GLOBAL : System = System;
fn main ( ) {
let values = vec! [ 10 , 20 , 30 ] ;
assert_eq! ( values.iter ( ) .sum:: < i32 > ( ) , 60 ) ;
}
自定义 GlobalAlloc 是 unsafe 实现责任:必须满足分配和释放契约,不能让这些方法 unwind。还要避免在分配器内部用可能再次分配的日志或容器,否则可能递归进入自己。
不要依赖“源码里写了 Box::new 就必定调用分配器一次”维持程序正确性。优化器可能消除或改变分配,分配次数可以用于受限制的观测,不能作为安全不变量。GlobalAlloc
Layout:大小、对齐和可表示范围
Layout::new::() 从类型获得布局,Layout::array::(n) 检查数组尺寸。from_size_align 要求对齐非零且为 2 的幂,并保证大小向对齐取整后的结果不超过 isize::MAX。
Layout 本身允许大小零,但 GlobalAlloc::alloc 的调用契约要求非零大小。不能把“成功创建 Layout”当成“可对任意分配 API 直接调用”。不同分配接口对零大小的规则需要分别核对。Layout
use std:: alloc:: Layout;
fn main ( ) {
assert! ( Layout:: from_size_align( 16 , 3 ) .is_err ( ) ) ;
assert! ( Layout:: array:: < u64 > ( usize :: MAX ) .is_err ( ) ) ;
let first = Layout:: from_size_align( 1 , 1 ) .unwrap ( ) ;
let next = Layout:: from_size_align( 4 , 4 ) .unwrap ( ) ;
let ( combined, offset) = first.extend ( next) .unwrap ( ) ;
let padded = combined.pad_to_align ( ) ;
assert_eq! ( offset, 4 ) ;
assert_eq! ( padded.size ( ) , 8 ) ;
assert_eq! ( padded.align ( ) , 4 ) ;
}
extend 返回第二部分的偏移,不应自己无检查地把字段大小相加。pad_to_align 补齐尾部,使连续对象也满足对齐;这个例子按显式 Layout 组合存储,不是在证明任意 Rust struct 都采用相同字段顺序。
尾部填充为什么影响数组步长
假设一条记录先放 4 字节、对齐 4 的字段,再放 1 字节字段。第二字段偏移是 4,组合的有效末端是 5;若紧接着在偏移 5 放下一条记录,其首字段就不满足 4 字节对齐。因此整体步长需要补成 8。
use std:: alloc:: Layout;
fn main ( ) {
let word = Layout:: from_size_align( 4 , 4 ) .unwrap ( ) ;
let tag = Layout:: from_size_align( 1 , 1 ) .unwrap ( ) ;
let ( record, tag_offset) = word.extend ( tag) .unwrap ( ) ;
assert_eq! ( tag_offset, 4 ) ;
assert_eq! ( record.size ( ) , 5 ) ;
assert_eq! ( record.pad_to_align ( ) .size ( ) , 8 ) ;
assert_eq! ( record.pad_to_align ( ) .align ( ) , 4 ) ;
}
这里的 3 字节尾部填充用于保证下一元素对齐,不承载业务字段。为外部协议编码时应写出字段,而不是把填充字节也作为稳定内容发送或哈希。布局算术解决空间安排,不解决填充是否初始化、值是否合法或指针能否访问的问题。
原始分配:先检查 null,再初始化
use std:: alloc:: { alloc, dealloc, handle_alloc_error, Layout} ;
fn main ( ) {
let layout = Layout:: new:: < u64 > ( ) ; let storage = unsafe { alloc ( layout) } ;
if storage.is_null ( ) {
handle_alloc_error ( layout) ;
}
let pointer = storage.cast:: < u64 > ( ) ;
let answer = unsafe {
pointer.write ( 42 ) ;
let value = pointer.read ( ) ;
dealloc ( storage, layout) ;
value
} ;
assert_eq! ( answer, 42 ) ;
}
alloc 返回的存储未初始化。普通赋值可能涉及替换旧值的语义,原始初始化应使用适当的 write 或 MaybeUninit 接口。对含资源的 T,必须明确 read 是否已移出所有权、是否还需要 drop_in_place,防止泄漏或重复析构。
dealloc 只释放存储,不会自动调用 T::drop。这个 u64 例子特意没有在分配和释放之间加入可失败业务;更复杂的构造需要处理部分初始化和 panic 路径,通常应交给 Box、Vec 或专门的 RAII 封装。
槽位 0 有效 T 槽位 1 有效 T 槽位 2 未初始化空间 槽位 3 未初始化空间 槽位 4 未初始化空间 len = 2:允许读取与析构 capacity = 5:可用存储,不代表已有 5 个 T Vec 的容量边界与有效元素边界必须分开 图示假设容量为 5;分配 API 的具体容量应读取 capacity,不从请求值反推。
Vec:先初始化,再扩展有效长度
Vec::with_capacity 预留空间,但 len 仍为 0,不能直接通过索引读取空槽。spare_capacity_mut 将空闲部分作为 [MaybeUninit<T>] 暴露,写入之后仍需更新 len,才把这些值纳入 Vec 的访问和析构责任。Vec 空闲容量
fn main ( ) {
let mut values = Vec : : < u32 > :: with_capacity( 5 ) ;
assert_eq! ( values.len ( ) , 0 ) ;
assert! ( values.capacity ( ) > = 5 ) ;
{
let spare = values.spare_capacity_mut ( ) ;
spare[ 0 ] .write ( 10 ) ;
spare[ 1 ] .write ( 20 ) ;
}
unsafe { values.set_len ( 2 ) ; }
assert_eq! ( values, [ 10 , 20 ] ) ;
values.push ( 30 ) ;
assert_eq! ( values, [ 10 , 20 , 30 ] ) ;
}
这是用于理解初始化边界的底层例子;普通追加直接 push 更容易维护。不能先 set_len 再逐项初始化:一旦中途 panic,Vec 可能尝试析构尚未有效的 T。反过来,若写入了 String 却始终没把它计入 len,Vec 不知道要析构它,可能泄漏其资源。
批量构造有可失败步骤时,优先让容器逐项接管已完成对象。下面在第二个元素处返回错误,已放入 Vec 的第一个对象会自动释放:
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 build ( drops : & Rc< Cell< usize > > ) -> Result< Vec< Tracked> , & 'static str > {
let mut items = Vec :: with_capacity( 3 ) ;
for index in 0 .. 3 {
if index = = 1 { return Err ( " 构造失败" ) ; }
items.push ( Tracked( Rc:: clone( drops) ) ) ;
}
Ok ( items)
}
fn main ( ) {
let drops = Rc:: new( Cell:: new( 0 ) ) ;
assert! ( build ( & drops) .is_err ( ) ) ;
assert_eq! ( drops.get ( ) , 1 ) ;
}
RAII 的价值就在于让错误路径与正常路径共享所有权规则。若确实需要原始批量初始化,应由专门 guard 记录已初始化数量,并在提前退出时只析构该前缀;不能把完整数组的析构逻辑应用于部分有效存储。
先分配 Box,再初始化值
fn main ( ) {
let mut storage = Box : : < u64 > :: new_uninit( ) ;
storage.write ( 42 ) ;
let value: Box< u64 > = unsafe { storage.assume_init ( ) } ;
assert_eq! ( * value, 42 ) ;
}
Box::new_uninit 返回 Box<MaybeUninit<T>>,assume_init 依赖调用方证明 T 已有效。只分配、未初始化就释放这个 Box 是允许的,但 MaybeUninit 不会自动替里面手动写入的 T 调用析构;初始化后仍要正确转移责任。零填充也不能代替此证明:全零不是任意 T 的有效表示。Box::new_uninit
容量、初始化与 len 的三条边界
把 len 当成容器作出的有效值承诺:它不仅决定索引范围,也决定正常析构哪些元素。分别移动初始化与 len 边界,就能看到两种不同错误:先扩大 len 会让容器相信未初始化的值存在;写好 String 却不接管它,则可能漏掉析构。普通 push 会把两步合在安全接口中。
正在呈现知识画面 重新加载
容量、初始化与 len 的三条边界 len 内必须全是有效值;len 外已写入的 String 不会由 Vec 自动析构。
{"id":"rust-initialization-prefix","title":"容量、初始化与 len 的三条边界","summary":"len 内必须全是有效值;len 外已写入的 String 不会由 Vec 自动析构。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"init\" data-i18n=\"init\"></label><output for=\"init\" id=\"init-value\"></output><input id=\"init\" type=\"range\" min=\"0\" max=\"6\" value=\"3\" step=\"1\"></div><div class=\"control\"><label for=\"length\" data-i18n=\"length\"></label><output for=\"length\" id=\"length-value\"></output><input id=\"length\" type=\"range\" min=\"0\" max=\"6\" value=\"2\" step=\"1\"></div><div class=\"track-row\"><span data-i18n=\"slots\"></span><div id=\"slots\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"drop\"></span><strong id=\"drop\"></strong></div><div><span data-i18n=\"leak\"></span><strong id=\"leak\"></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><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"lost\"></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)));function draw(){let i=n('init'),l=n('length');set('init-value',i);set('length-value',l);cells('slots',Array.from({length:6},(_,j)=>({text:j+'\\n'+(j<l?'len':'spare'),cls:j<i?'on':j<l?'error':'empty'})));set('drop',l);set('leak',Math.max(0,i-l));set('explanation',t(l>i?'bad':l<i?'lost':'ok'));document.body.dataset.valid=String(l<=i);}inputs(draw);","audio":false,"strings":{"bad":"危险:len 越过初始化边界;安全访问或析构会依赖不存在的有效 String。","drop":"Vec 应析构的槽数","heading":"容量、初始化与 len 的三条边界","init":"已初始化前缀","intro":"逐槽写入 String,再改变交给 Vec 管理的长度,观察析构责任。","leak":"未被 len 接管的 String","length":"拟设置的 len","lost":"这些额外 String 需要由其他 guard 接管;仅释放 Vec 不会析构 len 外的值。","ok":"所有 len 内元素有效,且没有遗漏已初始化的拥有值。","reset":"重置","scope":"固定容量 6 的 MaybeUninit<String> 教学模型。允许构造错误边界用于解释,浏览器不执行 UB。","scopeLabel":"模型范围与约定","slots":"槽位:青色有效,虚线未初始化"}}
全局替换与每容器分配器不是同一个 API
GlobalAlloc 和 #[global_allocator] 是稳定接口。标准库 Allocator trait 及许多 new_in 等按容器选择分配器的接口,在本次核对的文档中仍标记为 allocator_api 实验功能;不能把 nightly 示例直接称为稳定 Rust 用法。Allocator
选择分配器应依据碎片、延迟、线程竞争及工作负载测量,不根据名称承诺普遍更快。对于可预估的批量对象,首先考虑减少分配次数、预留容量或改变数据结构。
预留容量、释放容量与进程内存不是同一指标
操作或指标 可以据此判断什么 不能直接推出什么
Vec::clear 元素已被移除并按规则析构,len 归零 容量已归还给分配器
drop(Vec) 容器释放自己拥有的存储 操作系统 RSS 立即下降同样字节数
try_reserve 可处理容量溢出或分配失败的 Result 输入规模已经符合业务资源预算
shrink_to_fit 请求缩小多余容量 容量一定等于 len、内存一定立刻归还 OS
分配器可能缓存释放块,页面也可能混放仍存活对象,因此 RSS 与对象拥有的有效字节不同。分析“泄漏”时应区分仍可达对象持续增长、容器保留容量、分配器保留以及系统统计口径;只观察一次 RSS 不回落不足以定位责任。
预留输入声明的数量之前,先验证业务上限,再用 try_reserve 处理资源失败。即使大小算术不溢出,攻击性或错误输入仍可能要求不可接受的容量。泛型容器还要考虑零大小类型:它们的容量语义不能直接换算成真实申请字节。
repr 与 Pin 各自保证什么
repr(C) 约束结构布局遵循对应的 C 布局规则,但不自动递归改变字段类型的表示;repr(align(N)) 提高对齐,也可能增加填充。目标 ABI、字段有效值与端序仍需考虑,不能直接把结构内存当成跨平台序列化格式。
Pin 则表达地址相关的契约,不决定分配器,也不把一个类型自动变成不可移动。对于 Unpin 类型,仍可通过安全接口取到可移动的 &mut T;一个仅包含 String 和裸指针的自引用结构通常仍自动 Unpin,套上 Pin<Box<_>> 并不足以使其安全。
自引用抽象需要在稳定地址完成初始化、选择适当的 !Unpin 设计(如 PhantomPinned)、限制投影与移动,并遵守析构约定。固定的是 pointee;持有它的 Box 或 Pin 包装自身仍可移动。异步 Future 的相关机制见 async/await ,原始访问契约见 裸指针与 FFI 。
Pin 不禁止普通字段修改
use std:: pin:: Pin;
fn main ( ) {
let mut pinned = Box :: pin( String :: from( " old" ) ) ;
let text: & mut String = Pin:: as_mut( & mut pinned) .get_mut ( ) ;
let old = std:: mem:: replace( text, String :: from( " new" ) ) ;
assert_eq! ( old, " old" ) ;
assert_eq! ( pinned.as_ref ( ) .get_ref ( ) , " new" ) ;
}
String 是 Unpin,所以安全接口允许替换它。即使换成带 PhantomPinned 的自定义类型,也只是让相关移动入口受到限制;裸指针目标的初始化顺序、投影是否允许移动字段、析构期间是否保持契约,仍是抽象作者的责任。先明确哪些地址关系必须保持,再决定是否需要 Pin,不能把 Pin 当作通用的“堆对象加固”。
参考