An enum represents one of several possible shapes, where each variant can carry different data. Compared to a single state number plus a set of nullable fields, it allows "which fields exist in this state" to be expressed directly in the type.
Now Disconnected won't accidentally carry an uncleaned session field. However, the enum itself does not prevent the program from jumping from any state to another; to restrict transitions, you still need to encapsulate fields, methods, and construction entry points.
"Tagged unions" are a useful semantic model, but they do not guarantee that the physical storage has an independent tag. The actual size is also affected by the maximum payload, alignment, and niche optimization, so you cannot simply add them up mechanically, nor can you ignore the size of owning types like String.
Derive Types from Field Combinations Instead of Writing a Set of Options First
If you express three states using status: u8, address: Option<String>, and session: Option<u64>, field combinations could still result in scenarios like "connected but no session" or "offline but residual session data". An enum directly collects the allowed data for each shape into its variants, reducing the need for callers to repeatedly check conditions.
This is often referred to as a sum type: a value is in one of several candidates. A struct groups fields together, often called a product type. In simple examples of finite sets, two bools have 2×2=4 combinations; a Pending | Finished(bool) has 1+2=3 shapes. Here we are counting semantic values, not layout bytes.
Modeling Choice
Suitable For
Additional Constraints
Option
Present or absent
Absence reasons are usually not distinguished
Result<T, E>
Successful result or reportable error
Failure categories are designed by E
Custom enum
Different payloads per state
Transitions, resource ownership, and invariants
Multiple independent fields
Conditions that can truly be combined independently
Do not artificially prohibit valid combinations
Which combinations should never exist?
The table counts payload presence: three states times four combinations of two optional fields gives 12 shapes, of which only three are needed here. Select a combination to see how the enum excludes the others. These counts are neither object sizes nor counts including every possible string.
Preparing the visual
Which combinations should never exist?
Separate fields create 12 shapes; this enum permits only three payload shapes.
{"id":"rust-enum-state-space","title":"Which combinations should never exist?","summary":"Separate fields create 12 shapes; this enum permits only three payload shapes.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"off\" data-i18n=\"off\"></button><button data-mode=\"connecting\" data-i18n=\"connecting\"></button><button data-mode=\"connected\" data-i18n=\"connected\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"address\"><span data-i18n=\"address\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"session\"><span data-i18n=\"session\"></span></label><section class=\"panel\"><h3 data-i18n=\"shapes\"></h3><div id=\"space\"></div></section><section class=\"panel\"><h3 data-i18n=\"result\"></h3><div id=\"result\"></div></section><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.table .tr>*{min-height:3.4em}#result{min-height:4em;font:13px ui-monospace,monospace}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));let mode='off';function draw(){pressed(mode);let a=$('#address').checked,s=$('#session').checked,idx=['off','connecting','connected'].indexOf(mode),valid=(idx===0&&!a&&!s)||(idx===1&&a&&!s)||(idx===2&&a&&s);table('space',[['','0,0','0,1','1,0','1,1'],...['off','connecting','connected'].map((k,i)=>[t(k),...Array.from({length:4},(_,j)=>({text:(i===idx&&j===2*+a+ +s?'● ':'')+((i===0&&j===0)||(i===1&&j===2)||(i===2&&j===3)?'✓':'×'),cls:(i===idx&&j===2*+a+ +s)?'gold':''}))])]);set('result',valid?['Disconnected','Connecting { address }','Connected { peer, session }'][idx]:'—');set('explanation',t(valid?'ok':'bad'));document.body.dataset.valid=valid;}document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;draw()});inputs(draw);","audio":false,"strings":{"address":"Has address / peer","bad":"A plain struct can hold this combination, but this enum cannot construct that inconsistent state.","connected":"Connected","connecting":"Connecting","heading":"Which combinations should never exist?","intro":"Choose status and the presence of address and session; compare separate fields with an enum.","mode":"State","off":"Offline","ok":"The selected combination has a variant with exactly the required payload.","reset":"Reset","result":"Corresponding enum","scope":"Counts field presence, not all String and u64 values. The enum itself does not restrict transitions.","scopeLabel":"Model scope and assumptions","session":"Has session","shapes":"All field shapes"}}
State Transitions: Consuming the Old Value and Clarifying Ownership on Failure
The following conversion function takes Job by value. On success, the old state is consumed, and data moves into the new state; on failure, it returns the original state and an error, allowing the caller to continue editing or logging without having to clone the String for the failure path. The completion event also carries a ticket, preventing completion events for old tasks from incorrectly advancing the current task.
The guard here checks the condition when deciding whether to select a branch; if the condition fails, it can still fall through to subsequent matching branches. When completion is successful, text is no longer needed, so the old text is released along with the consumed state; if the business logic still requires auditing the full text, it should be saved in the new state or an external record, and you cannot expect the enum to automatically retain it.
This function guarantees the listed local transition rules, but it does not execute external tasks, verify the global uniqueness of the ticket, or persist the result. If Job and its variant constructors are all public, other code can still bypass the function and construct Done directly; enforcing entry points externally requires module encapsulation. If you want to prevent calling completion operations on Draft at compile time, you can further use different types of typestate interfaces, at the cost of more complex runtime state sets and heterogeneous container handling.
Exhaustive Checking of Possible Shapes in match
An exhaustive match without a fallback will prompt you to add branches after new variants are added. A match that already uses _ can continue to compile, so you cannot say that all match sites will error due to new variants. Public non_exhaustive enums also require downstream code to reserve matching ways for future extensions.
match searches for matching branches in source code order. A guard is a boolean condition checked additionally after a pattern succeeds; the compiler generally does not treat the logic of arbitrary guards as a proof of complete coverage. match
fnclassify(input:Option<i32>)->&'staticstr{match input {Some(value @1..=5)if value %2==1=>"small odd",Some(_)=>"other number",None=>"missing",}}fnmain(){assert_eq!(classify(Some(3)),"small odd");assert_eq!(classify(Some(2)),"other number");assert_eq!(classify(None),"missing");}
@ simultaneously saves the matched value and checks the sub-pattern. if let is suitable for processing only one shape, while while let is suitable for repeatedly fetching the same type of result; ignoring the rest should be the intent of the interface, and you cannot treat syntactic brevity as proof that error handling is complete.
Exhaustiveness is a Proof of Coverage, Not a Proof of Business Correctness
Some(x) if x > 0 and Some(x) if x <= 0 look like they cover all i32s, but the compiler does not universally prove the complementary relationship of arbitrary guards, and corresponding guard-less covering branches are still required. A _ => {} will allow compilation to pass, but it may silently ignore newly added important states.
Each branch of an or-pattern must bind the same name, compatible types, and binding patterns, e.g., Some(1 | 2) only tests the value, while Ok(value) | Err(value) also requires the value types on both sides to be consistent. @ binds the value while checking the sub-pattern, rather than copying an arbitrarily large data instance.
The slice pattern example only checks the shape and start marker, and does not actually verify the checksum. Patterns are good at describing structure, but business relationships still need to be calculated explicitly. Ordinary let requires non-refutable patterns; Some(x), which may not match, is better suited for match, if let, or let-else.
When matching against &item, the match ergonomics rules cause the inner binding to borrow the field; destructuring item by value for non-Copy fields transfers ownership. let-else requires the else branch to diverge, e.g., return, break, or panic, so that subsequent paths can rely on the pattern having succeeded.
_ in a pattern does not create a binding; _name still creates a variable, which can move and own the value. Rust 2024 has stricter rules for explicitly writing ref/mut combinations in some implicit borrowing patterns; clearly choosing to match &value vs value is usually more readable. Patterns
Look at the Matched Expression and Binding Type Together
Input and Syntax
Type/Behavior of text
Original Container Retained?
match item, Some(text)
String moved by value
The moved part can no longer be used as usual
match &item, Some(text)
&String
Retained, borrow rules apply during the borrow
match &mut item, Some(text)
&mut String
Retained, allows modifying fields via the borrow
match item.as_deref()
&str inside Option<&str>
Retained, does not transfer string ownership
item.take()
Takes the original value of the Option
The original position becomes None
mut text allows the binding to be reassigned, which is not equivalent to "borrowing a &mut String"; ref mut text is what requests a mutable borrow in the corresponding by-value pattern context. Rust 2024 restricts certain combinations of writing ref/mut in default borrowing modes, prioritizing explicit selection of matching &item or &mut item to make the input hierarchy clear. Rust 2024 Match Rules
fnmain(){letmut item =Some(String::from("hello"));ifletSome(text)=&mut item {
text.push('!');}assert_eq!(item.as_deref(),Some("hello!"));let taken = item.take().unwrap();assert_eq!(taken,"hello!");assert!(item.is_none());let ignored =Some(String::from("still here"));ifletSome(_)= ignored {assert_eq!(ignored.as_deref(),Some("still here"));}// Some(_) does not bind and move the String; Some(_text) would bind that field.
assert!(ignored.is_some());}
You cannot directly move the String out of &mut Option<String> and leave an uninitialized hole; take() solves this by placing a valid None in its place. For other types, you can look into mem::replace, but the replacement value should be a state allowed by the business logic, not a meaningless state invented just to bypass borrow errors. Option::take
Partial moves also require distinguishing between "whether the entire value can still be used" and "whether the un-moved fields can still be used". After moving a String field from a struct without a custom Drop, other un-moved fields may still be accessible; borrowing the entire struct or calling methods that depend on the complete value is different. Types with custom Drop also have restrictions on directly moving out their fields.
Option and Result are Common Enum Protocols
Ordinary references cannot be null, but Rust's raw pointers allow null values, so "Rust has no null" is an overgeneralization. Option allows normal safe interfaces to express absence, and callers can still choose unwrap and panic on None; the type does not automatically choose a recovery plan for them.
Only checking b == 0 misses the overflow of dividing the minimum signed integer by -1. If the caller needs to distinguish between the two types of failure, they should return a Result with categories, rather than compressing all reasons into None.
Result incorporates reportable failures into the function's return type, but it does not guarantee the absence of internal panics or external side effects. See Result and Option for error propagation and composition methods.
Evolution of Public Enums and Wire Decoding
#[non_exhaustive] mainly constrains usage outside the defining crate; the crate itself can still fully match its currently defined variants. Downstream code needs to reserve branches for unknown variants, but this is not permission to turn arbitrary integers into valid enums at runtime. non_exhaustive
If wire status codes allow for future additions, you can save unknown numeric values as a distinct Unknown(u8) variant. This is completely different from an illegal discriminant in a Rust enum: Unknown is a defined, valid variant that carries the raw code for logging, forwarding, or subsequent processing.
#[derive(Debug, PartialEq)]enumStatus{ Ready, Busy, Unknown(u8)}implFrom<u8>forStatus{fnfrom(code:u8)->Self{match code {1=>Self::Ready,2=>Self::Busy,
other =>Self::Unknown(other),}}}fnmain(){assert_eq!(Status::from(1),Status::Ready);assert_eq!(Status::from(2),Status::Busy);assert_eq!(Status::from(255),Status::Unknown(255));}
This interface choice retains unknown codes, but operations that require understanding to execute should still be rejected. For inputs that do not allow unknown values, implement TryFrom and return an error. Do not use transmute to interpret arbitrary bytes as a finite variant enum; having the same layout size does not prove the discriminant is valid.
The combination of enums and match brings shape checking, field access, and ownership transfer together. When using them, confirm in order: which states require different data; which transitions are allowed; whether branches explicitly cover important cases; whether bindings are moving or borrowing; and how unknown inputs enter valid types. This makes it easy to quickly locate syntax while keeping the model consistent with business behavior.