You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
V version: V 0.5.1 99f141f74 (verified no relevant commits land between that and current master b6dfae560)
OS-agnostic
Context
docs.md §Closures (lines 3245–3266) documents that [mut x] makes the closure's copy mutable but does not capture x by reference, so writes inside the closure are invisible to the caller. This is by design and works for stateful counter patterns. It is, however, a silent footgun for the much more common pattern of "let a helper closure update some local I'm building up."
The footgun
Downstream code routinely writes:
mut cur := ?T(none)
flush := fn [mut cur] (val T) {
if mut t := cur { /* mutate t */; cur = t }
}
for v in items { flush(v) }
// cur is silently unchanged.
The closure compiles, runs, and the mutation lands on the closure's private copy. The outer cur is never updated. There is no warning; the surface syntax is identical to what would be a sound pattern in most languages with explicit by-reference syntax (Rust &mut, C++ &, Swift inout).
In CX (a V-native data interchange library) this exact pattern landed in three conformance-test runners and silently made every fixture mismatch pass for one release cycle. The discovery was an accident.
Possible fixes
Listed in order of preference:
Reference-capture syntax — e.g. [&mut x], [ref x], or something else V-shaped. Makes the by-reference intent explicit at the capture site; today there is no way to express it.
Compile-time warning — when a captured [mut x] is written to inside the closure but never re-read outside the closure's own call sites, emit "captured copy of x is modified but the change does not propagate; pass x as a mut parameter to a regular function instead." Closes the footgun without changing semantics.
Documentation-only fix — surface the gotcha more prominently in docs.md §Closures and link it from the troubleshooting page. The current paragraph is technically accurate but easy to skim past.
Workaround used downstream
Rewrite the closure as a regular fn (...mut cur ...) function and call it explicitly. Works fine but breaks the closure-shaped reading of the call site.
Note
You can use the 👍 reaction to increase the issue's priority for developers.
Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.
This discussion was converted from issue #27172 on May 24, 2026 20:28.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Environment
V 0.5.1 99f141f74(verified no relevant commits land between that and current masterb6dfae560)Context
docs.md§Closures (lines 3245–3266) documents that[mut x]makes the closure's copy mutable but does not capturexby reference, so writes inside the closure are invisible to the caller. This is by design and works for stateful counter patterns. It is, however, a silent footgun for the much more common pattern of "let a helper closure update some local I'm building up."The footgun
Downstream code routinely writes:
The closure compiles, runs, and the mutation lands on the closure's private copy. The outer
curis never updated. There is no warning; the surface syntax is identical to what would be a sound pattern in most languages with explicit by-reference syntax (Rust&mut, C++&, Swiftinout).In CX (a V-native data interchange library) this exact pattern landed in three conformance-test runners and silently made every fixture mismatch pass for one release cycle. The discovery was an accident.
Possible fixes
Listed in order of preference:
[&mut x],[ref x], or something else V-shaped. Makes the by-reference intent explicit at the capture site; today there is no way to express it.[mut x]is written to inside the closure but never re-read outside the closure's own call sites, emit "captured copy ofxis modified but the change does not propagate; passxas amutparameter to a regular function instead." Closes the footgun without changing semantics.docs.md§Closures and link it from the troubleshooting page. The current paragraph is technically accurate but easy to skim past.Workaround used downstream
Rewrite the closure as a regular
fn (...mut cur ...)function and call it explicitly. Works fine but breaks the closure-shaped reading of the call site.Note
You can use the 👍 reaction to increase the issue's priority for developers.
Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.
Beta Was this translation helpful? Give feedback.
All reactions