struct describes data and type identity, while trait describes required behaviors, associated types, and constants. Rust connects behavior to types via impl, eliminating the need to organize all reuse relationships through a class inheritance tree.
Three Structure Types and New Types
Named fields are suitable for data where field meaning matters; tuple structs are suitable for wrapping where position is explicit; unit structs have no fields and can serve as markers or stateless strategies. type Meters = f64 is merely an alias, whereas struct Meters(f64) creates a distinct type.
modunits{#[derive(Debug, Clone, Copy, PartialEq)]pubstructMeters(f64);implMeters{pubfnnew(value:f64)->Option<Self>{if value.is_finite()&& value >=0.0{Some(Self(value))}else{None}}pubfnvalue(self)->f64{self.0}}}fnmain(){let distance =units::Meters::new(12.5).unwrap();assert_eq!(distance.value(),12.5);assert!(units::Meters::new(-1.0).is_none());assert!(units::Meters::new(f64::NAN).is_none());}
Outside the module, private fields cannot be constructed or modified directly, so values can only be created through specified entry points. New types prevent passing Feet directly as Meters parameters, but if raw fields are exposed and added arbitrarily, unit errors can still occur.
Single-field wrappers do not automatically guarantee the same ABI as the inner type; if you need to satisfy specific layout constraints, look into repr(transparent) rather than treating "zero-cost" as a guarantee for all representation details.
New type invariants must cover all modification entry points
Meters::new rejects negative numbers, NaN, and infinity, establishing the invariant that "the current value is finite and non-negative." If an add method is added later and performs direct f64 addition, the sum of two finite values might still overflow to infinity, so the method should also re-validate the result.
Whether to provide DerefMut<Target = f64> also affects encapsulation: it would allow callers to write arbitrary f64 values directly, bypassing construction validation. Serialization/deserialization, From/TryFrom, default values, and arithmetic traits are also creation or modification entry points; simply making fields private does not automatically complete the entire invariant proof.
The following example uses a finite integer range to illustrate the failure behavior of "validate first, assign later." When set returns an error, the old value is preserved, so callers don't have to guess whether the object was partially modified after failure.
modlimits{#[derive(Debug, PartialEq)]pubstructPercent(u8);implTryFrom<u16>forPercent{typeError=&'staticstr;fntry_from(value:u16)->Result<Self, Self::Error>{if value <=100{Ok(Self(value asu8))}else{Err("out of range")}}}implPercent{pubfnvalue(&self)->u8{self.0}pubfnset(&mutself, value:u16)->Result<(), &'staticstr>{let checked =Self::try_from(value)?;*self= checked;Ok(())}}}fnmain(){letmut value =limits::Percent::try_from(40).unwrap();assert_eq!(value.set(101),Err("out of range"));assert_eq!(value.value(),40);
value.set(100).unwrap();assert_eq!(value.value(),100);}
From is suitable for cases with no failure and conforming to conversion semantics, while TryFrom explicitly writes potential failures into the type; do not silently truncate illegal values into another meaning just for the convenience of .into(). Here, as u8 is placed within the branch already checked for ≤100, and the safe range is established by preconditions. TryFrom
Struct update syntax does not call Clone
Config { retries: 5, ..old } only retrieves fields from old that are not explicitly provided: Copy fields are copied, and other fields are moved. It does not clone the entire object, nor does it inherit from another instance.
structConfig{endpoint: String, retries:u8}fnmain(){let old = Config { endpoint:"localhost".into(), retries:2};let next = Config { retries:5,..old };assert_eq!(next.endpoint,"localhost");assert_eq!(next.retries,5);// endpoint has been moved, but retries was not moved, so it can be read separately.
assert_eq!(old.retries,2);}
You can no longer borrow the entire old to print or call methods that depend on the complete object. The update syntax is also subject to rules such as field visibility and Drop; outside the module, you cannot bypass private fields to construct an object via ..old. Struct update
Trait methods can have default implementations, and associated types are usually specified by the implementation; default methods can use capabilities already promised by the trait. &self borrowing, &mut self exclusive borrowing, and self consuming the value are all important components of interface behavior.
The compiler can check signatures and type requirements, but it does not prove all business contracts. For example, if a method promises ordered results, or that Eq/Hash must be consistent, these semantics are still maintained by the implementer. A trait is not just a list of function names. Traits
Receivers are also resource lifetime design
Borrowing and consuming are not just writing preferences. finish(self) indicates that the object has entered its final stage, and the caller hands over ownership; finish(&mut self) requires defining what usable state the object remains in after completion. For reusable buffers, the latter might be appropriate; for builders that cannot be resubmitted, the former expresses intent more clearly.
Shared references do not absolutely mean "the method makes no modifications": interior mutability types like Cell, RefCell, and Mutex allow changing internal state according to their own rules. Trait documentation should supplement constraints regarding threads, reentrancy, post-error state, and side effects; you cannot infer pure functions solely from &self.
Same method names: eliminate ambiguity with fully qualified syntax
traitHumanLabel{fnlabel(&self)->&'staticstr;}traitMachineLabel{fnlabel(&self)->&'staticstr;}structDevice;implHumanLabel forDevice{fnlabel(&self)->&'staticstr{"device"}}implMachineLabel forDevice{fnlabel(&self)->&'staticstr{"dev-7"}}fnmain(){let value = Device;assert_eq!(<Device as HumanLabel>::label(&value),"device");assert_eq!(<Device as MachineLabel>::label(&value),"dev-7");}
Method calls combine auto-deref, auto-borrowing, and search for candidate traits visible in the scope; "always prefer inherent methods first" is an oversimplification because different receiver candidates have a priority order. When ambiguity exists, explicitly specifying type, trait, and receiver is more helpful than blindly adding clone. Method lookup
derive: generates implementations, but also introduces constraints
When switching to f64 fields, directly deriving Eq and Hash becomes unavailable; floating-point semantics like NaN require explicit handling and cannot be mechanically copied from integer type derivation lists. Deriving Clone is also not an automatic recursive "deep copy": cloning an Arc field still only increases the shared owner count.
derive is a macro mechanism: the standard library provides a set of built-in derives, while Serialize etc. come from third-party procedural macros. Automatically generated generic constraints are sometimes more conservative than manual implementations; when necessary, you should check the actual constraints and write manual implementations, rather than arbitrarily adding Clone to all generic parameters.
Derive constraints may be stronger than what the fields actually require
For example, Arc<T> can be cloned even if T does not implement Clone, because it copies shared ownership, not T itself. Directly deriving Clone for a generic wrapper containing Arc<T> may generate implementations that require T: Clone; a manual implementation can express more accurate constraints.
usestd::sync::Arc;structShared<T>(Arc<T>);impl<T> Clone forShared<T>{fnclone(&self)->Self{Self(Arc::clone(&self.0))}}structNotClone{value:u32}fnmain(){let a = Shared(Arc::new(NotClone { value:7}));let b = a.clone();assert!(Arc::ptr_eq(&a.0,&b.0));assert_eq!(b.0.value,7);assert_eq!(Arc::strong_count(&a.0),2);}
Here, NotClone does not have Clone added, nor is the underlying object copied. Whether to share, copy independently, or prohibit copying depends on resource semantics; you cannot treat "derive can generate" as a reason for interface selection. Clone derive
Eq and Hash are a shared contract across methods
If a == b, they must produce the same hash; the reverse is not true because hash collisions are allowed. If equality is determined only by user_id, but the mutable display_name is also hashed, two keys deemed equal might fall into different lookup paths, breaking the usage assumptions of HashMap/HashSet.
Desired Semantics
Implementation Direction
All fields participate in value equality
Uniformly derive PartialEq/Eq/Hash for fields that meet the conditions
Equal only by stable ID
Use the same set of identity fields for comparison and hashing
Case-insensitive identifier
Normalize at construction, or use consistent comparison/hashing rules
Allow floating-point special values
First choose semantics for NaN, ±0, etc., then choose wrapping and implementation
After a key is inserted into a collection, you should not change the content determining Eq/Hash through interior mutability. The standard library treats such contract violations as logic errors; contract violations for generally safe traits should not be assumed by unsafe code as impossible, leading to memory unsafety. Hash contract, PartialEq contract
Why can an equal key miss?
A trait contract can span implementations. Case-insensitive Eq with case-sensitive Hash may send equal keys to different candidates. The model deliberately permits that broken combination to separate type checking from behavioral consistency. Hash collisions are allowed; the implication runs from equality to equal hashes.
k1=k2⟹H(k1)=H(k2)
Preparing the visual
Why can an equal key miss?
Keys equal under Eq must hash equally; otherwise lookup may visit a different candidate bucket.
{"id":"rust-eq-hash-contract","title":"Why can an equal key miss?","summary":"Keys equal under Eq must hash equally; otherwise lookup may visit a different candidate bucket.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"form-field\"><span data-i18n=\"key\"></span><input type=\"text\" id=\"key\" value=\"Rust\" maxlength=\"30\" autocomplete=\"off\" spellcheck=\"false\"></label><label class=\"form-field\"><span data-i18n=\"query\"></span><input type=\"text\" id=\"query\" value=\"rust\" maxlength=\"30\" autocomplete=\"off\" spellcheck=\"false\"></label><label class=\"check\"><input type=\"checkbox\" id=\"normal\" checked><span data-i18n=\"normal\"></span></label><div class=\"track-row\"><span data-i18n=\"buckets\"></span><div id=\"buckets\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"eq\"></span><strong id=\"eq\"></strong></div><div><span data-i18n=\"hash\"></span><strong id=\"hash\"></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=\"diff\"></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 a=$('#key').value,b=$('#query').value,lower=s=>s.replace(/[A-Z]/g,c=>c.toLowerCase()),norm=$('#normal').checked,H=s=>[...s].reduce((h,c)=>(h+c.charCodeAt(0))%251,0),ha=H(norm?lower(a):a),hb=H(norm?lower(b):b),eq=lower(a)===lower(b);cells('buckets',Array.from({length:7},(_,i)=>({text:i+' '+(ha%7===i?'I':'')+(hb%7===i?'Q':''),cls:ha%7===i?'on':hb%7===i?'gold':''})));set('eq',String(eq));set('hash',ha+' / '+hb);set('explanation',t(!eq?'diff':ha===hb?'ok':'bad'));document.body.dataset.valid=String(!eq||ha===hb);}inputs(draw);","audio":false,"strings":{"bad":"Eq is true but hashes differ, violating the Eq/Hash contract. The container cannot repair the custom implementation.","buckets":"Candidate buckets: I insert, Q query","diff":"The keys are unequal. Even in the same bucket, equality must still be checked.","eq":"Eq result","hash":"Same hash","heading":"Why can an equal key miss?","intro":"Use ASCII-insensitive equality, then change whether hashing uses the same rule.","key":"Inserted key","normal":"Hash also lowercases","ok":"Equality and hashing use the same normalization, satisfying the contract for this key pair.","query":"Lookup key","reset":"Reset","scope":"An seven-bucket additive teaching hash, not std HashMap’s algorithm, capacity or probing policy.","scopeLabel":"Model scope and assumptions"}}
Orphan rule and implementation overlap
Trait implementations must satisfy both the orphan rule and non-conflict requirements. Roughly speaking, the trait must be locally defined, or involve a local type that conforms to the rules; generic implementations also have overlap and parameter ordering requirements. Checks occur during compiler trait coherence rules, not by waiting for the linker to randomly pick an implementation. Implementation coherence
Implementation Form
Typical Result in Current Crate
External trait for local MyType
Allowed, but still avoid overlap
Local MyTrait for external String
Allowed, but still avoid overlap
External Display for external String
Not allowed
External Display for Vec
Not allowed; Vec does not become a local type just because it contains local types internally
fundamental types like Box have specific handling in the rules and cannot be generalized to "any generic container is local as long as its parameters are local." When you need to provide your own display for external containers, a new type is usually clearer:
Even if the trait is local, impl<T> MyTrait for T might overlap with a more specific implementation; the orphan rule does not mean arbitrary overriding of valid implementations is allowed.
Constraints hand implementation capabilities to generic callers
T: Summary + Clone means requiring both capabilities simultaneously; where clauses are simply a writing style better suited for complex relationships. Parent traits express prerequisite capabilities, not inheritance structure fields. Constraints on associated types and dyn object limitations are covered in Associated Types and GATs and Dynamic Dispatch respectively.
Design public traits: treat the implementable space as part of the API
Defining a trait is equivalent to allowing callers to depend on it, and potentially allowing downstream users to implement it for their own types. Adding methods without default implementations will leave downstream implementations incomplete; adding default methods can also bring name conflicts, behavioral changes, or dyn compatibility changes, so you cannot simply assume "having a default implementation means no risk."
Overriding implementations like impl<T: Display> MyTrait for T provide capabilities for a large class of types, but also occupy the space for these types to write specific MyTrait implementations. It is not a runtime plugin priority rule; you cannot expect more specific impl to automatically override generic impl; stable Rust's coherence checks will reject such overlaps.
If you only intend to allow the library itself to implement it, you can adopt an intentional sealed trait design; if you want external extension, try to keep required methods few and stable, and place composable convenience methods in reasonable positions. Both closed and open approaches have their uses, and documentation should explain which capabilities are commitments and which are extension points. Cargo SemVer and trait changes
The most valuable aspect of combining struct and trait is separating the definition of data ownership, invariants, and behavior contracts, and then connecting them via controlled impl interfaces. Even if an interface passes type checking, it still requires consistent equality/hash, post-failure state, and evolution conventions for callers to rely on it reliably.