Gotchas
A checklist of idioms that trip people up, each with the working shape. Grown as findings land.
Arriving with an error message in hand? The error index is organized by message instead of by topic.
Language
matchcan’t be an operator operand.(match x { … }) + 1→ bind the match to a local first.- A bare integer literal adapts to its peer; two typed variables
don’t.
stamp + 1000andstamp < 1000are fine on ani53(the literal takes the peer’s type), but mixing two differently-typed variables in a comparison or an addition is an error: there are no implicit conversions. Convert withas_*or unify the declarations. - Concatenation renders nothing for you, and an i-string is
concatenation.
"p=" + pointandi"p={point}"are the same expression, and both refuse a value with no string form rather than printing its runtime shape (p=1,2). Callto_string()— writing animpl … with Displayif the type has none. The string also has to be on the left: an expression takes its type from its left operand, socount + "!"is an error, not ani32holding"1!". - A guarded last
matcharm proves nothing. A guard tests the value and exhaustiveness reasons about the type, soB if ready => …leavesBmissing. Write the arm you meant to fall through to:B if ready => …, _ => …. - An exhaustive
matchover a backed enum can still panic. The enum is its backing string or number at run time, so a host value outside the set (anexternal funreturn, a callback’s argument) reaches the trap arm:Align: "middle" is not one of its values. Give it a_arm, or take the value throughAlign::parse, which answersNone. - A view may not be held across a suspension, not just an
await. Calling an async function without the keyword suspends identically — and so does a sync-looking function that reaches one. - Writing over a resource-holding place destroys what it replaces.
slot.held = Holder::Emptyruns the old value’s destructor before the write, through a&mutview and on an owned place alike. That is the rule working; it is worth knowing it is when teardown happens.
Reactive & UI
shared.read()is a copy.shared.read().push(x)is lost; write through the cell:shared.write().push(x).- Mutate signal collections with
update—signal.update(|&mut list| { list.push(x); })writes the stored value through a view and notifies once. Never mutate aget()result (also a copy).set_withstays the read-transform-write form. bind_valuefights remote updates. For server-backed fields usebind_draft.showkeeps bindings live while hidden; usewhento drop state and subscriptions.- Disposal doesn’t cancel the in-flight wave: a subscriber already queued in the draining turn may fire once more; only later deliveries are guaranteed gone.
Services & the wire
- Contract-mismatch errors on connect usually mean a leaked old server
still holding the port:
ss -tlnp | grep <port>, kill by PID. descis an SQL keyword. Name the columndescription(any SQL keyword as a column name fails inCREATE TABLE).- Value semantics cross the wire: a mirrored list is a fresh copy per update; mutate via rpcs, never by writing the client’s mirror signal.
Process & testing
- A completed Node
mainexits the process. Long-lived clients/servers must holdmainopen. - Process-target artifacts are
.mjs, not.js.vilan build app.vlwritesapp.mjs, and a multi-entry package writesdist/<name>.mjsper process entry — sonode dist/server.jsbecomesnode dist/server.mjsin scripts, Dockerfiles and process managers. A browser leg keeps.js: its<script type="module">declares the module at the load site, where the extension carries no weight. - A module-level binding cannot
await, in any spelling. Not the implicit await of an async call, and not an explicitawaiton aTask, a spawn, or anasync { … }block. Spawn at module level (let pending = async work();) and do the waiting insideasync fun main(). - A server should not read its own build by hand.
require_build,require_shellandserve_builddescribe it from what the build actually wrote; a path typed from memory leaves a renamed leg compiling and the page blank (Persistence). pkill -f <pattern>can match your own shell’s command string. Kill by tracked PID.- Rebuild the debug binary before regenerating corpus goldens. A stale binary silently writes wrong goldens.