Skip to content

strings.Builder.write_decimal: add an unsigned u64 variant and JS-backend parity #27510

Description

@enghitalo

Describe the feature

strings.Builder already has a zero-allocation decimal writer for signed integers —
pub fn (mut b Builder) write_decimal(n i64) at vlib/strings/builder.c.v:78 (added in
#19625). This request is for two narrow parity gaps around it:

  1. No unsigned u64 variant. The current API is i64-typed, so values above
    max_i64 cannot be written allocation-free; callers must fall back to
    write_string(n.str()), which allocates.
  2. The JS backend has no write_decimal at all. write_decimal lives only in
    vlib/strings/builder.c.v. The JS-backend builder (vlib/strings/builder.js.v) does
    not implement it, so code relying on it is not portable across the C and JS backends.

Use Case

Hot paths that serialize many integers into a buffer (HTTP response building, code
generation, logging) use write_decimal to avoid the intermediate string from
write_string(n.str()). Two situations are currently unserved:

  • Code that must also build on the JS backend cannot use write_decimal (compile error /
    missing method), forcing a backend-specific workaround.
  • Code that needs the full unsigned u64 range (counters, hashes, ids > max_i64)
    cannot use the zero-alloc path and must allocate via .str().

Proposed Solution

For reference, the existing C-backend implementation
(vlib/strings/builder.c.v:74-103):

// write_decimal appends a decimal representation of the number `n` into the builder `b`,
// without dynamic allocation.
@[direct_array_access]
pub fn (mut b Builder) write_decimal(n i64) {
	if n == 0 {
		b.write_u8(0x30)
		return
	}
	if n == min_i64 {
		b.write_string(n.str())
		return
	}
	mut buf := [25]u8{}
	mut x := if n < 0 { -n } else { n }
	mut i := 24
	for x != 0 {
		nextx := x / 10
		r := x % 10
		buf[i] = u8(r) + 0x30
		x = nextx
		i--
	}
	if n < 0 {
		buf[i] = `-`
		i--
	}
	unsafe { b.write_ptr(&buf[i + 1], 24 - i) }
}
  1. Unsigned variant — add pub fn (mut b Builder) write_u_decimal(n u64) using the
    same stack-buffer approach but without the sign branch and without the min_i64
    special case, covering the full u64 range allocation-free.
  2. JS-backend parity — mirror write_decimal (and the new unsigned variant) into
    vlib/strings/builder.js.v so the API is available on both backends.

Other Information

  • Existing implementation: vlib/strings/builder.c.v:74-103.
  • Existing test: vlib/strings/builder_test.v:160 (test_write_decimal).
  • Existing stdlib consumers: vlib/net/http/response.v:50, vlib/veb/veb.v:856,
    and the cgen.
  • Originating merged PR: strings: add Builder.write_decimal/1 #19625 "strings: add Builder.write_decimal/1".
  • Purely additive; no behavior change to the existing write_decimal.

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Feature/Enhancement RequestThis issue is made to request a feature or an enhancement to an existing one.Unit: JSBugs/feature requests, that are related to the JavaScript backend.Unit: vlibBugs/feature requests, that are related to the vlib.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions