Skip to content

slices.Grow implementation mismatch with upstream Go #5155

@soypat

Description

@soypat

This is a TinyGo bug where slices.Grow allocates more capacity than requested. While Go's spec says Grow increases capacity by "at least n", both implementations should behave consistently for code that relies on the exact capacity returned.

func TestSlicesGrow(t *testing.T) {
	var s []int
	s = slices.Grow(s, 1)
	t.Logf("cap after Grow(nil, 1): %d", cap(s))
	if cap(s) != 1 {
		t.Errorf("cap is %d, expected 1", cap(s))
	}
}

Result:

go test ./dns && tinygo test ./dns
ok      github.com/soypat/lneto-bug/dns 0.001s
--- FAIL: TestSlicesGrow (0.00s)
    cap after Grow(nil, 1): 2
    cap is 2, expected 1
FAIL
FAIL    github.com/soypat/lneto-bug/dns 0.000s

A even more reduced implementation of the bug:

func TestAppendSpread(t *testing.T) {
	s := append([]int(nil), make([]int, 1)...)
	if cap(s) != 1 {
		t.Errorf("cap is %d, expected 1", cap(s))
	}
}

According to Claude:
Root cause: TinyGo's append with spread operator (...) doesn't optimize for exact capacity when appending a known-size slice to nil.

Go recognizes this pattern and allocates exactly the needed capacity, while TinyGo applies its growth strategy (doubling), resulting in cap=2 instead of cap=1. This is a TinyGo compiler/runtime bug in how it handles append with the spread operator.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions