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
error('msg') heap-allocates a MessageError on every call and boxes it into
the IError interface. This happens on the hot path of any !T ("result") function
that returns an error, even when the caller discards the result with or {} —
the allocation happens inside the callee, before it returns, regardless of what the
caller does with the value.
Current implementation, vlib/builtin/chan_option_result.v:128-134:
The & heap-allocates a MessageError (GC-managed). The compiler lowers error(...)
to builtin___v_error(...). Generated C (v -printfn builtin___v_error file.v):
HEAP(type, expr) expands to ((type*)builtin__memdup((void*)&((type[]){expr}[0]), sizeof(type)))
— a real allocation (builtin__memdup) plus interface boxing, per call.
This contrasts with none, which already has a zero-allocation cached sentinel
(vlib/builtin/chan_option_result.v:203-211):
A very common pattern is a "lookup / not-found" function on a hot path:
fnfind(x int) !int {
if x <0 {
returnerror('not found')
}
return x
}
fnmain() {
mutn:=0fori:=-1_000_000; i <0; i++ {
find(i) or { n++ } // result discarded, yet each error() above still allocated
}
println(n)
}
Every miss allocates a MessageError on the heap, even though the error object is
immediately discarded. For tight loops, caches, parsers, and any "fast not-found path",
this is a per-call allocation that cannot currently be avoided while still using the
idiomatic !T / error() mechanism.
Generated main__find (identical under default and-prod):
The callee always builds the boxed error (builtin___v_error → HEAP(...) → builtin__memdup) before returning, so or {} at the call site cannot avoid it.
V already proves a zero-alloc error return is possible: ?T functions that return none compile to a cached const, no allocation:
There is no equivalent for the very common "return an error with no extra state" case.
Proposed Solution
Provide a non-allocating, cached/sentinel error path for the common stateless-error
case, mirroring how none works. Possible approaches (any one would address this):
A cached/static sentinel error for the stateless case (e.g. a public error_none constant, or reusing a single static IError), so a "not found" fast
path can be expressed allocation-free.
A non-allocating error() for compile-time-constant messages. String literals
are already static, so the MessageError could be emitted as a static instance
instead of HEAP(...) when the message is a constant.
A compiler optimization: when the result of error(...) is provably discarded
by the caller's or {} (message unused), skip the HEAP allocation and return a
cached sentinel.
The none machinery in vlib/builtin/chan_option_result.v:203-211 is the reference
implementation pattern.
Exact code involved: vlib/builtin/chan_option_result.v:129-134 (error), its lowering
to builtin___v_error, and the matching HEAP(MessageError, ...) expansion above.
Other Information
Reproduction verified with v run, v -printfn ..., and v -prod on the version below.
Swapping !int → ?int and return error('not found') → return none removes the
allocation (.err=_const_none__), demonstrating both the asymmetry and that a zero-alloc
path is feasible.
No existing duplicate was found via gh search issues --repo vlang/v for terms like
"error allocation", "MessageError", "zero allocation error", "cached error",
"IError boxing".
Describe the feature
error('msg')heap-allocates aMessageErroron every call and boxes it intothe
IErrorinterface. This happens on the hot path of any!T("result") functionthat returns an error, even when the caller discards the result with
or {}—the allocation happens inside the callee, before it returns, regardless of what the
caller does with the value.
Current implementation,
vlib/builtin/chan_option_result.v:128-134:The
&heap-allocates aMessageError(GC-managed). The compiler lowerserror(...)to
builtin___v_error(...). Generated C (v -printfn builtin___v_error file.v):HEAP(type, expr)expands to((type*)builtin__memdup((void*)&((type[]){expr}[0]), sizeof(type)))— a real allocation (
builtin__memdup) plus interface boxing, per call.This contrasts with
none, which already has a zero-allocation cached sentinel(
vlib/builtin/chan_option_result.v:203-211):Use Case
A very common pattern is a "lookup / not-found" function on a hot path:
Every miss allocates a
MessageErroron the heap, even though the error object isimmediately discarded. For tight loops, caches, parsers, and any "fast not-found path",
this is a per-call allocation that cannot currently be avoided while still using the
idiomatic
!T/error()mechanism.Generated
main__find(identical under default and-prod):The callee always builds the boxed error (
builtin___v_error→HEAP(...)→builtin__memdup) before returning, soor {}at the call site cannot avoid it.V already proves a zero-alloc error return is possible:
?Tfunctions thatreturn nonecompile to a cached const, no allocation:There is no equivalent for the very common "return an error with no extra state" case.
Proposed Solution
Provide a non-allocating, cached/sentinel error path for the common stateless-error
case, mirroring how
noneworks. Possible approaches (any one would address this):error_noneconstant, or reusing a single staticIError), so a "not found" fastpath can be expressed allocation-free.
error()for compile-time-constant messages. String literalsare already static, so the
MessageErrorcould be emitted as a static instanceinstead of
HEAP(...)when the message is a constant.error(...)is provably discardedby the caller's
or {}(message unused), skip theHEAPallocation and return acached sentinel.
The
nonemachinery invlib/builtin/chan_option_result.v:203-211is the referenceimplementation pattern.
Exact code involved:
vlib/builtin/chan_option_result.v:129-134(error), its loweringto
builtin___v_error, and the matchingHEAP(MessageError, ...)expansion above.Other Information
Reproduction verified with
v run,v -printfn ..., andv -prodon the version below.Swapping
!int→?intandreturn error('not found')→return noneremoves theallocation (
.err=_const_none__), demonstrating both the asymmetry and that a zero-allocpath is feasible.
No existing duplicate was found via
gh search issues --repo vlang/vfor terms like"error allocation", "MessageError", "zero allocation error", "cached error",
"IError boxing".
Acknowledgements
Version used
V 0.5.1 6dd9033.eb1d47b
Environment details (OS name and version, etc.)
linux, Ubuntu 24.04 LTS; cc (GCC) 14.2.0
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.