slices.Grow reserves room for future appends without changing a slice’s length. The distinction between length and capacity is central to its contract: existing elements remain the logical contents, while the returned slice can accept at least a requested number of additional elements without another allocation.

The generic signature is:

func Grow[S ~[]E, E any](s S, n int) S

The return value matters because increasing capacity can require a new backing array.

Grow reserves space beyond the current length

The argument n describes additional elements, not a target capacity. For a slice with length 3, slices.Grow(s, 5) guarantees enough capacity to append at least five more elements.

package main

import (
    "fmt"
    "slices"
)

func main() {
    values := []int{10, 20, 30}
    values = slices.Grow(values, 5)

    fmt.Println(len(values))
    fmt.Println(cap(values) >= len(values)+5)
}

The length remains 3. The capacity is at least 8, although code should not depend on a particular larger capacity chosen by the implementation.

This makes Grow different from extending a slice with append. Reserving capacity does not create new logical elements and does not expose zero-value slots through the slice’s current length.

Existing capacity can satisfy the request

A call does not require allocation when the slice already has enough unused capacity.

values := make([]int, 2, 10)
values[0] = 4
values[1] = 7

values = slices.Grow(values, 6)

fmt.Println(len(values)) // 2
fmt.Println(cap(values)) // at least 8

The existing capacity of 10 already leaves room for eight additional elements, so the requested guarantee is satisfied without needing more space.

The API guarantees append capacity, not allocation behavior as an observable contract. Code should use the capacity guarantee rather than depend on backing-array identity.

A returned slice can point at new storage

When current capacity is insufficient, Grow may return a slice backed by a different array. Reassigning the result is therefore required.

values := []int{1, 2, 3}
values = slices.Grow(values, 100)
values = append(values, 4, 5)

Ignoring the returned value can discard the increased-capacity slice header. This is the same practical rule that applies to append: an operation that can replace backing storage must return the slice value that describes the usable result.

Existing elements are preserved in the returned slice. Grow changes available capacity, not the values within the current length.

Capacity reservation can separate aliases

Two slices can initially share a backing array. If Grow must allocate for one of them, the returned slice can become independent at the outer storage level.

base := []int{1, 2, 3}
alias := base

base = slices.Grow(base, 100)
base[0] = 9

fmt.Println(base[0])  // 9
fmt.Println(alias[0]) // 1 when growth moved base to new storage

That separation is a consequence of allocation, not an alias-breaking guarantee that callers should request through Grow. If the original slice already has enough capacity, shared backing storage can remain shared. slices.Clone is the direct operation when separate outer slice storage is the actual requirement.

Reference-bearing elements also retain their own reference relationships. Moving a slice to a new backing array copies element values; it does not recursively copy objects reached through pointers, maps, slices, or other references.

Nil state is preserved

A nil slice remains nil when no extra capacity is requested:

var values []int
values = slices.Grow(values, 0)

fmt.Println(values == nil) // true

Requesting positive capacity from a nil slice requires storage for future appends, so the returned slice has zero length with available capacity.

var values []int
values = slices.Grow(values, 4)

fmt.Println(len(values))   // 0
fmt.Println(cap(values) >= 4) // true

The nil-preservation guarantee applies when the operation can satisfy the request without introducing storage.

Invalid growth requests panic

A negative n is invalid and causes a panic. A request that is too large to allocate also panics.

values := []byte{1, 2}
_ = slices.Grow(values, -1) // panic

Code that derives n from external data or arithmetic should validate the value before calling Grow when panic-based failure does not fit the surrounding API.

A large positive request also represents a real memory commitment if current capacity cannot satisfy it. Grow is capacity reservation for a materialized slice, not a deferred promise of storage.

Reservation is useful when append volume is already known

Repeated append calls already handle slice growth, so Grow is not required for correctness in ordinary append-heavy code. Its narrower role appears when code knows a lower bound for upcoming additions and wants that capacity available before those appends occur.

func addBatch(dst []int, batch []int) []int {
    dst = slices.Grow(dst, len(batch))
    return append(dst, batch...)
}

After the Grow call, appending all elements from batch fits within the guaranteed capacity. The exact total capacity remains an implementation detail.

slices.Grow is therefore best read as a capacity contract rather than a resizing operation. It preserves the current logical slice, returns a header with room for at least n more elements, and leaves the actual capacity value flexible beyond that guarantee.