Generics allow an algorithm to apply to a set of types that satisfy constraints. Constraints serve both parties: the caller must provide a type that meets the requirements, and the function body can only rely on the capabilities promised by those requirements. The compiler will not allow string-specific operations that were not declared, even if a particular call happens to pass a String.
From Definition Checking to Instantiation
fnchoose<T: Ord>(a: T, b: T)-> T{if a >= b { a }else{ b }}fnmain(){assert_eq!(choose(3,8),8);assert_eq!(choose(String::from("a"),String::from("z")),"z");}
This algorithm requires a total order comparison, hence the Ord requirement. Changing it to PartialOrd requires explicitly defining a strategy for incomparable values, such as floating-point NaN; you cannot uniformly interpret a > b being false as a <= b.
rustc collects generic instances for concrete types as needed, then proceeds to code generation. Lifetime parameters primarily serve static checking; machine code is not generated for every possible borrow duration. Monomorphization
Instances during generation do not correspond one-to-one with final symbols: unused code may not exist, function bodies may disappear after inlining, and identical implementations may be merged. You cannot assert that there are three independent functions in the binary just based on three calls.
Constraints are not implicit specialization inferred backwards from current calls
When defining fn f<T>(value: T), T represents any type satisfying known constraints. Even if all current calls pass String, the function body cannot directly call String-specific methods; you need to declare the actual requirements, such as AsRef<str>, or write the parameter directly as &str. This allows callers to understand the scope of applicability just by looking at the signature.
Similarly, T: Add only indicates that an addition implementation exists; it does not guarantee that the result is still T. If the algorithm requires the result to be closed within T, you should write the associated output constraint Add<Output = T>. Generic constraints should stem from the operations and type relationships required by the algorithm, rather than blindly adding Clone, Copy, and Default whenever a compiler error occurs. Trait bounds
The same algorithm, ownership choices change the interface
The choose function above takes two Ts by value and returns the winner, dropping the other value; it has no Clone constraint because it does not require copying values. If the caller only wants to compare existing objects while keeping them, they can provide a borrowing version.
fnchoose_ref<'a, T: Ord +?Sized>(a:&'a T, b:&'a T)->&'a T{if a >= b { a }else{ b }}fnmain(){let first =String::from("alpha");let second =String::from("omega");assert_eq!(choose_ref(&first,&second),"omega");assert_eq!(first,"alpha");assert_eq!(second,"omega");// str is a DST, the function only borrows it; ?Sized relaxes the implicit Sized constraint.
assert_eq!(choose_ref::<str>("a","b"),"b");}
The 'a constraint here ensures the returned reference does not outlive the borrow of the input; it does not extend the lifetime of the two string objects; nor does it require the two objects to have the same lifetime from birth to destruction. At the call site, you can re-borrow within the shorter range where both are valid.
?Sized does not require T to be dynamically sized; rather, it allows T to not satisfy the implicit Sized bound. The function still accesses T via a reference, and the reference itself is a value of known size; this does not allow arbitrary unknown-sized T to be handled directly by value as local variables.
Interface
What is given up
Applicable goal
fn choose<T: Ord>(a: T, b: T) -> T
Ownership of both values
When the other input is no longer needed after selection
fn choose_ref(... &T ...) -> &T
Shared borrow of inputs
Do not copy, keep both original values
fn choose_cloned<T: Ord + Clone>(...) -> T
Borrow, and copy the selected value
Explicitly need an independent return value or shared ownership defined by Clone
If the actual need is just to output the name, but you add Clone to return T, then copy a large buffer, the generic signature may mask unnecessary work. Determine the ownership of the return value first, then choose trait constraints.
Static dispatch and the actual meaning of "zero cost"
Concrete T provides the compiler with layout and method implementation information, facilitating inlining and optimization; this does not guarantee inlining will happen, nor does it guarantee that the algorithm, memory allocation, or bounds checking have no cost. You should compare the overhead with a concrete implementation of equivalent semantics.
Generics can also handle trait objects: T: Trait + ?Sized can take T = dyn Trait, where internal methods may still use dynamic dispatch. Static generics and dynamic objects can be combined; they are not mutually exclusive language patterns.
PartialOrd gaps require explicit handling
Floating-point numbers contain NaN, so there is only a partial order. When a comparison results in None, the algorithm can reject, skip, or adopt a definite total order; these strategies are not automatically obtained by changing Ord to PartialOrd.
This version discards both inputs and returns None when they are incomparable; if the caller wants to keep them, the error branch should carry the original values. Traits express "what operations can be done," but the function must also clarify how the operation results affect the business logic.
Controlling the scope of generic propagation
If a large part of the work no longer depends on T, you can move it into a concrete type function. The following function accepts different AsRef<str> inputs, but the scanning logic only needs &str:
This structure reduces the propagation of type dependencies; whether it actually reduces machine code depends on inlining and optimization. Allocating a String for each element first to eliminate generics may introduce higher runtime costs; you should prioritize borrowing or existing common representations.
Common representations should preserve original semantics
Converting multiple types to &str allows sharing logic that depends only on strings; but if the input is a path, raw bytes, or a structured object, forcibly converting to String may cause allocation, loss of non-UTF-8 information, or loss of errors that could originally be expressed. Boundary shrinking should occur where the algorithm truly only needs the common representation.
Cross-crate generics also participate in code generation based on actual instances; you cannot simply assume that because the dependency library was compiled once, all downstream instances no longer need processing. On the other hand, different lifetime parameters usually do not result in separate copies of machine code, and untriggered type combinations should not be mechanically counted in the output.
Where does generic propagation stop?
This compares how far type information propagates. When the algorithm only needs &str, a thin generic entry can borrow that representation and call a shared scanner. Block counts do not predict final machine-code copies: inlining, elimination and merging still apply.
Preparing the visual
Where does generic propagation stop?
After expressing shared scanning as &str, only the thin adapter depends on T.
{"id":"rust-generic-boundary","title":"Where does generic propagation stop?","summary":"After expressing shared scanning as &str, only the thin adapter depends on T.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"count\" data-i18n=\"count\"></label><output for=\"count\" id=\"count-value\"></output><input id=\"count\" type=\"range\" min=\"1\" max=\"4\" value=\"3\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"split\" checked><span data-i18n=\"split\"></span></label><section class=\"panel\"><h3 data-i18n=\"instances\"></h3><div id=\"entries\"></div></section><section class=\"panel\"><h3 data-i18n=\"core\"></h3><div id=\"core\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"full\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"thin\"></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.table .tr>*{min-height:3.8em}#entries{min-height:190px}#entries .tr>*{min-height:5.4em}#core{min-height:7em}","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 k=n('count'),split=$('#split').checked;set('count-value',k);table('entries',Array.from({length:4},(_,i)=>[i<k?['String','&str','Box<str>','Cow<str>'][i]:'—',i<k?(split?'as_ref() → scan(&str)':'scan<T'+i+'>'): '—']));cells('core',Array.from({length:4},(_,i)=>({text:split?(i===0?'scan(&str)':'·'):(i<k?'scan<T'+i+'>':'·'),cls:(split?i===0:i<k)?'on':'empty'})));set('explanation',t(split?'thin':'full'));document.body.dataset.contexts=split?1:k;}inputs(draw);","audio":false,"strings":{"core":"Type dependencies of scanning","count":"Concrete caller types","full":"Each used T puts the scan body in a concrete type context; optimization need not preserve that many functions.","heading":"Where does generic propagation stop?","instances":"Type-dependent entries","intro":"Change the number of caller types and whether the heavy loop still depends on T.","reset":"Reset","scope":"Blocks denote type dependencies before final optimization, not symbol counts, bytes or performance predictions.","scopeLabel":"Model scope and assumptions","split":"Extract the loop as scan(&str)","thin":"Each T only adapts via as_ref; scanning shares an &str interface. Measure the actual size effect."}}
Use associated types to constrain input items, rather than fixing containers
If the algorithm only needs to iterate over u32, there is no need to force receiving Vec<u32>; IntoIterator<Item = u32> allows arrays, Vec, or other sources that produce u32 by value. The default iteration item for borrowed slices is &u32; in this case, you can explicitly use .copied() to make "whether to copy elements" appear at the call site.
Here, converting u32 to u64 does not lose individual numeric values, but infinite or sufficiently long inputs may still cause the sum to overflow; if the total might exceed u64, you need checked accumulation and error returns. Generalizing the input container does not expand the range of numerical safety.
const generics: constants are also type parameters
Buffer<u8, 4> and Buffer<u8, 8> are different types; N is not a field that changes arbitrarily at runtime. When runtime length is needed, Vec or slices are usually more suitable; do not force all dynamic requirements into type parameters.
There are boundaries to the types of parameters and expressions supported by stable const generics. Using N directly in an array length is not equivalent to using N + 1; arbitrary generic constant expressions cannot be assumed to be stably available. Generic parameter rules
Length in type parameters vs. runtime length
The N in Buffer<u8, 4> is fixed when the instance type is determined, so the compiler can know the layout; len obtained from user input cannot be directly written as Buffer<u8, len>. If only a few fixed sizes are supported, you can branch at runtime to select different static instances; if sizes vary arbitrarily, Vec, slices, or a fixed maximum buffer with an effective length are more natural.
Const parameters do not automatically prove that all arithmetic is valid. N=0 may be legal or may violate some algorithmic requirements; overflow of indices like N-1, N*M, and the resource overhead of large arrays on the stack still need explicit handling. Moving numbers into types does not mean all business conditions become compile-time proofs.
impl Trait parameters are chosen by the caller
impl Trait in the parameter position expresses an anonymous generic parameter, such as impl AsRef<str> above. Two independent impl Trait parameters do not require the concrete types to be the same; writing two parameters as the same T requires them to unify to the same type.
impl Trait in the return position is chosen by the function implementation to hide the concrete type; it does not mean that any implementing type can be returned at every run. See Associated Types and GATs for more.
Two anonymous parameters do not express "the same type"
The two implementing types in show_pair can be different; same_pair requires them to unify to the same T in this call. If you need to describe type relationships between parameters or return values, explicitly naming T is often clearer. impl Trait in the parameter position is not dyn Trait, and it will not automatically become dynamic dispatch just because the type name is omitted. impl Trait
Specialization and coherence
Stable Rust does not allow arbitrarily overriding a general impl with a more specific impl where they overlap. specialization is still an experimental feature, and min_specialization should not be treated as a stable interface promise. Unstable Book
Stable solutions can use newtypes, explicit strategy parameters, or different entry points to ensure implementation ranges do not overlap. Choosing these solutions is to clarify the interface; it does not guarantee automatically getting all the effects of compiler specialization. When focusing on performance, observe compilation time, artifact size, and actual hot paths simultaneously, rather than just counting the number of generic parameters.
How to observe costs, rather than estimating machine code count from source code
Cost
Observation metric
Variables easily mixed in
Compilation time
Compare clean builds and incremental builds with the same configuration
Caches, parallelism, dependency versions
Artifact size
Record executable code section, debug info, and total file size separately
strip, LTO, panic strategy, linker
Runtime
Same data, semantics, and workload
Allocation, I/O, cold/hot cache, branch ratio
Hot path dispatch
Assembly/IR combined with actual performance sampling
Results after inlining and devirtualization
You can use rustc --edition=2024 -O --emit=llvm-ir example.rs to view the optimized IR of a standalone example, but you should not write the number of symbol searches as a language guarantee. Instantiation count is part of the compiler's workload; final code may still be inlined, eliminated, or merged.
If splitting a large loop into a non-generic function builds faster but the hot path slows down, you should check if optimizations previously brought by type information were lost; conversely, adding dynamic calls may improve instruction cache due to smaller code size. Choosing static or dynamic boundaries requires looking at interface expression, compilation cost, and actual load simultaneously, rather than treating "monomorphization" as a winning label.