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
There is currently no public, zero-allocation integer-to-text formatter in the
stdlib that writes digits into a caller-provided []u8 buffer and returns the number
of bytes written. Every path from an integer to its textual form heap-allocates:
int.str() / i32.str() / i16.str() / u16.str() go through str_l(max), which
does malloc_noscan(max + 1) and returns tos(buf, diff) — vlib/builtin/int.v:94.
i64.str() / int_literal.str() go through impl_i64_to_string, which does malloc_noscan(max + 1) — vlib/builtin/int.v:221.
u32.str() / u64.str() each malloc_noscan — vlib/builtin/int.v:172, :267.
String interpolation allocates too. With v -printfn main__main:
The actual digit-emitting core is already zero-alloc internally: strconv.format_dec_sb
fills a stack[32]u8{} buffer (vlib/strconv/format_mem.c.v:97-122). The only
thing missing is a public variant whose target is a caller buffer rather than a
heap-backed strings.Builder.
Use Case
Hot loops and allocation-sensitive code (logging, serializers, network/codec encoders,
embedded / -gc none, arena allocators) need to render integers into an already-owned
buffer without per-value heap traffic. Today anyone who wants this must hand-roll a
private helper (a write_decimal / emit_int that indexes the digit table and writes
into a []u8), duplicating logic that already lives — but is not reachable — inside strconv.format_dec_sb.
Note: strings.Builder already has a zero-alloc write_decimal(n i64)
(vlib/strings/builder.c.v:78), which solves the Builder target. What is still
missing is the lower-level primitive that writes into a plain []u8 (no Builder, no
backing heap array at all).
Proposed Solution
Add public buffer-target formatters in strconv that write into a caller-provided slice
and return the count, allocating nothing:
// returns the number of bytes written, or -1 if buf is too small.pub fnwrite_dec(n i64, mut buf []u8) intpub fnwrite_dec_u(n u64, mut buf []u8) int// optional: arbitrary radix without per-digit concatenationpub fnwrite_int(n i64, radix int, mut buf []u8) int
The implementation already exists in spirit — strconv.format_dec_sb
(vlib/strconv/format_mem.c.v:44-133) builds digits into a stack [32]u8{}
(lines 97-122) and then writes into a strings.Builder. The new API would do the same
digit loop but copy into the caller's []u8, using strconv.dec_digits(n)
(vlib/strconv/utilities.v:183) to bounds-check the destination first. No malloc_noscan and no strings.Builder would be involved.
Sketch:
@[direct_array_access]
pub fnwrite_dec_u(n u64, mut buf []u8) int {
nd:=dec_digits(n)
if buf.len < nd {
return-1
}
muttmp:= [20]u8{}
muti:=20mutv:= n
for {
i--
tmp[i] =u8(v %10) +`0`
v /=10ifv==0 {
break
}
}
unsafe { vmemcpy(&buf[0], &tmp[i], nd) }
return nd
}
A signed wrapper would emit - then delegate, with the existing min_i64 special case
from vlib/builtin/int.v:217.
Describe the feature
There is currently no public, zero-allocation integer-to-text formatter in the
stdlib that writes digits into a caller-provided
[]u8buffer and returns the numberof bytes written. Every path from an integer to its textual form heap-allocates:
int.str()/i32.str()/i16.str()/u16.str()go throughstr_l(max), whichdoes
malloc_noscan(max + 1)and returnstos(buf, diff)—vlib/builtin/int.v:94.i64.str()/int_literal.str()go throughimpl_i64_to_string, which doesmalloc_noscan(max + 1)—vlib/builtin/int.v:221.u32.str()/u64.str()eachmalloc_noscan—vlib/builtin/int.v:172,:267.v -printfn main__main:Generated C:
builtin__int_str→str_l→malloc_noscan(vlib/builtin/int.v:94).builtin__str_intp→strings.new_builder(64)+res.str()(heap), seevlib/builtin/string_interpolation.v:698-716.The actual digit-emitting core is already zero-alloc internally:
strconv.format_dec_sbfills a stack
[32]u8{}buffer (vlib/strconv/format_mem.c.v:97-122). The onlything missing is a public variant whose target is a caller buffer rather than a
heap-backed
strings.Builder.Use Case
Hot loops and allocation-sensitive code (logging, serializers, network/codec encoders,
embedded /
-gc none, arena allocators) need to render integers into an already-ownedbuffer without per-value heap traffic. Today anyone who wants this must hand-roll a
private helper (a
write_decimal/emit_intthat indexes the digit table and writesinto a
[]u8), duplicating logic that already lives — but is not reachable — insidestrconv.format_dec_sb.Note:
strings.Builderalready has a zero-allocwrite_decimal(n i64)(
vlib/strings/builder.c.v:78), which solves the Builder target. What is stillmissing is the lower-level primitive that writes into a plain
[]u8(no Builder, nobacking heap array at all).
Proposed Solution
Add public buffer-target formatters in
strconvthat write into a caller-provided sliceand return the count, allocating nothing:
The implementation already exists in spirit —
strconv.format_dec_sb(
vlib/strconv/format_mem.c.v:44-133) builds digits into a stack[32]u8{}(lines 97-122) and then writes into a
strings.Builder. The new API would do the samedigit loop but copy into the caller's
[]u8, usingstrconv.dec_digits(n)(
vlib/strconv/utilities.v:183) to bounds-check the destination first. Nomalloc_noscanand nostrings.Builderwould be involved.Sketch:
A signed wrapper would emit
-then delegate, with the existingmin_i64special casefrom
vlib/builtin/int.v:217.Other Information
string." — improved the speed of the allocating
str()path; did not add abuffer-target API.
strconv.format_decis public butBF_paramnot #5152 "strconv.format_decis public butBF_paramnot" — adjacent;a new API could reuse
strconv.BF_param.natural first step.
.str()/interpolation.
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.