Kevin Burke
Kevin Burke

Reputation: 65044

Concatenate two slices in Go

I'm trying to combine the slice [1, 2] and the slice [3, 4]. How can I do this in Go?

I tried:

append([]int{1,2}, []int{3,4})

but got:

cannot use []int literal (type []int) as type int in append

However, the documentation seems to indicate this is possible, what am I missing?

slice = append(slice, anotherSlice...)

Upvotes: 847

Views: 691025

Answers (11)

qpaycm
qpaycm

Reputation: 930

In case you want to append a slice

arr := new([][]string)
*arr = append(*arr, []string{"abc", "def"})
fmt.Println(*arr)

See playground

Upvotes: -1

VonC
VonC

Reputation: 1329942

With Go 1.22 (Q1 2024), you might consider the new func Concat[S ~[]E, E any](slices ...S) S generic function.

See commit 2fd195, which fixes issue 56353

// Join slices into a new slice
a := []int{ 1, 2, 3 }
b := []int{ 4, 5, 6 }
c := slices.Concat(nil, a, b) 
// c == int{ 1, 2, 3, 4, 5, 6 }

s := [][]int{{1}, nil, {2}}
c = slices.Concat(s...)
// c == int{1, 2}

This is being followed with "New API changes for Go 1.22" (issue 64343).


As noted by Eric in the comments, slices.Concat() always allocates a fresh slice to hold all elements from the passed-in slices; it does not modify or extend any of them.

So if you need in-place appends (and you’re fine with changing the original slices), you can still rely on append().
Otherwise, slices.Concat() is useful when you want a new independent slice, leaving existing slices unaltered.

See playground

// File name: concat_test.go (for go test)
//
// Demonstrates that slices.Concat() creates an entirely new slice
// without altering the originals, whereas an in-place append will
// modify the original slice if capacity permits.

package main

import (
    "fmt"
    "reflect"
    "slices"
    "testing"
)

// TestMyConcatBasic checks a single case to illustrate that
// slices.Concat() returns a fresh slice and does not modify 'a' or 'b'.
func TestMyConcatBasic(t *testing.T) {
    a := []int{1, 2}
    b := []int{3, 4}
    want := []int{1, 2, 3, 4}

    // Copy 'a' and 'b' so we can verify they are not changed.
    oldA := make([]int, len(a))
    copy(oldA, a)
    oldB := make([]int, len(b))
    copy(oldB, b)

    got := slices.Concat(a, b)
    if !reflect.DeepEqual(got, want) {
        t.Errorf("Concat(%v, %v) = %v; want %v", a, b, got, want)
    }

    // Check 'a' and 'b' remain unmodified after slices.Concat.
    if !reflect.DeepEqual(a, oldA) {
        t.Errorf("original slice 'a' changed: got %v, want %v", a, oldA)
    }
    if !reflect.DeepEqual(b, oldB) {
        t.Errorf("original slice 'b' changed: got %v, want %v", b, oldB)
    }
}

// TestMyConcatTableDriven uses a table-driven approach to verify
// slices.Concat() in multiple scenarios, confirming each time that
// the source slices remain unmodified.
func TestMyConcatTableDriven(t *testing.T) {
    var tests = []struct {
        a, b []int
        want []int
    }{
        // Edge cases
        {[]int{}, []int{}, []int{}},
        {[]int{}, []int{1}, []int{1}},
        // Typical usage
        {[]int{1}, []int{2, 3}, []int{1, 2, 3}},
        {[]int{1, 2}, []int{3, 4}, []int{1, 2, 3, 4}},
        // Another example
        {[]int{1, 2, 3}, []int{4, 5, 6}, []int{1, 2, 3, 4, 5, 6}},
    }

    for i, tt := range tests {
        testName := fmt.Sprintf("TestCase#%d", i)
        t.Run(testName, func(t *testing.T) {
            // Make a copy of the original slices.
            oldA := make([]int, len(tt.a))
            copy(oldA, tt.a)
            oldB := make([]int, len(tt.b))
            copy(oldB, tt.b)

            got := slices.Concat(tt.a, tt.b)
            if len(got) == 0 {
                got = []int{}
            }
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("%s: Concat(%v, %v) = %v; want %v",
                    testName, tt.a, tt.b, got, tt.want)
            }

            // Confirm that Concat did not mutate the inputs.
            if !reflect.DeepEqual(tt.a, oldA) {
                t.Errorf("original slice a changed: got %v, want %v", tt.a, oldA)
            }
            if !reflect.DeepEqual(tt.b, oldB) {
                t.Errorf("original slice b changed: got %v, want %v", tt.b, oldB)
            }
        })
    }
}

// TestInPlaceAppend shows how the native append() can modify the
// original slice in place.
func TestInPlaceAppend(t *testing.T) {
    a := []int{1, 2}
    oldA := make([]int, len(a))
    copy(oldA, a)

    // In-place append: modifies the underlying array if capacity allows.
    a = append(a, 3, 4)
    if reflect.DeepEqual(a, oldA) {
        t.Errorf("expected 'a' to be changed, but it's still %v", a)
    }
    if !reflect.DeepEqual(a, []int{1, 2, 3, 4}) {
        t.Errorf("in-place append failed; got %v, want %v", a, []int{1, 2, 3, 4})
    }
}

Upvotes: 11

user1106925
user1106925

Reputation:

Add dots after the second slice:

//                           vvv
append([]int{1,2}, []int{3,4}...)

This is just like any other variadic function.

func foo(is ...int) {
    for i := 0; i < len(is); i++ {
        fmt.Println(is[i])
    }
}

func main() {
    foo([]int{9,8,7,6,5}...)
}

Upvotes: 1537

PeterM
PeterM

Reputation: 2702

Seems like a perfect use for generics (if using 1.18 or later).

func concat[T any](first []T, second []T) []T {
    n := len(first);
    return append(first[:n:n], second...);
}

Upvotes: 6

Gandharva S Murthy
Gandharva S Murthy

Reputation: 315

To concatenate two slices,

func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{99, 100}
    s1 = append(s1, s2...)

    fmt.Println(s1) // [1 2 3 99 100]
}

To append a single value to a slice

func main() {
    s1 :=  []int{1,2,3}
    s1 := append(s1, 4)
    
    fmt.Println(s1) // [1 2 3 4]
}

To append multiple values to a slice

func main() {
    s1 :=  []int{1,2,3}
    s1 = append(s1, 4, 5)
    
    fmt.Println(s1) // [1 2 3 4]
}

Upvotes: 7

D.C. Joo
D.C. Joo

Reputation: 1317

I would like to emphasize @icza answer and simplify it a bit since it is a crucial concept. I assume that reader is familiar with slices.

c := append(a, b...)

This is a valid answer to the question. BUT if you need to use slices 'a' and 'c' later in code in different context, this is not the safe way to concatenate slices.

To explain, lets read the expression not in terms of slices, but in terms of underlying arrays:

"Take (underlying) array of 'a' and append elements from array 'b' to it. If array 'a' has enough capacity to include all elements from 'b' - underlying array of 'c' will not be a new array, it will actually be array 'a'. Basically, slice 'a' will show len(a) elements of underlying array 'a', and slice 'c' will show len(c) of array 'a'."

append() does not necessarily create a new array! This can lead to unexpected results. See Go Playground example.

Always use make() function if you want to make sure that new array is allocated for the slice. For example here are few ugly but efficient enough options for the task.

la := len(a)
c := make([]int, la, la + len(b))
_ = copy(c, a)
c = append(c, b...)

la := len(a)
c := make([]int, la + len(b))
_ = copy(c, a)
_ = copy(c[la:], b)

Upvotes: 83

ASHWIN RAJEEV
ASHWIN RAJEEV

Reputation: 2891

append( ) function and spread operator

Two slices can be concatenated using append method in the standard golang library. Which is similar to the variadic function operation. So we need to use ...

package main

import (
    "fmt"
)

func main() {
    x := []int{1, 2, 3}
    y := []int{4, 5, 6}
    z := append([]int{}, append(x, y...)...)
    fmt.Println(z)
}

output of the above code is: [1 2 3 4 5 6]

Upvotes: 10

BaSO4
BaSO4

Reputation: 81

append([]int{1,2}, []int{3,4}...) will work. Passing arguments to ... parameters.

If f is variadic with a final parameter p of type ...T, then within f the type of p is equivalent to type []T.

If f is invoked with no actual arguments for p, the value passed to p is nil.

Otherwise, the value passed is a new slice of type []T with a new underlying array whose successive elements are the actual arguments, which all must be assignable to T. The length and capacity of the slice is therefore the number of arguments bound to p and may differ for each call site.

Given the function and calls

func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")

Upvotes: 3

icza
icza

Reputation: 418705

I think it's important to point out and to know that if the destination slice (the slice you append to) has sufficient capacity, the append will happen "in-place", by reslicing the destination (reslicing to increase its length in order to be able to accommodate the appendable elements).

This means that if the destination was created by slicing a bigger array or slice which has additional elements beyond the length of the resulting slice, they may get overwritten.

To demonstrate, see this example:

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

Output (try it on the Go Playground):

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 10
x: [1 2 3 4]
a: [1 2 3 4 0 0 0 0 0 0]

We created a "backing" array a with length 10. Then we create the x destination slice by slicing this a array, y slice is created using the composite literal []int{3, 4}. Now when we append y to x, the result is the expected [1 2 3 4], but what may be surprising is that the backing array a also changed, because capacity of x is 10 which is sufficient to append y to it, so x is resliced which will also use the same a backing array, and append() will copy elements of y into there.

If you want to avoid this, you may use a full slice expression which has the form

a[low : high : max]

which constructs a slice and also controls the resulting slice's capacity by setting it to max - low.

See the modified example (the only difference is that we create x like this: x = a[:2:2]:

a := [10]int{1, 2}
fmt.Printf("a: %v\n", a)

x, y := a[:2:2], []int{3, 4}
fmt.Printf("x: %v, y: %v\n", x, y)
fmt.Printf("cap(x): %v\n", cap(x))

x = append(x, y...)
fmt.Printf("x: %v\n", x)

fmt.Printf("a: %v\n", a)

Output (try it on the Go Playground)

a: [1 2 0 0 0 0 0 0 0 0]
x: [1 2], y: [3 4]
cap(x): 2
x: [1 2 3 4]
a: [1 2 0 0 0 0 0 0 0 0]

As you can see, we get the same x result but the backing array a did not change, because capacity of x was "only" 2 (thanks to the full slice expression a[:2:2]). So to do the append, a new backing array is allocated that can store the elements of both x and y, which is distinct from a.

Upvotes: 39

fiatjaf
fiatjaf

Reputation: 12187

Nothing against the other answers, but I found the brief explanation in the docs more easily understandable than the examples in them:

func append

func append(slice []Type, elems ...Type) []Type The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice. It is therefore necessary to store the result of append, often in the variable holding the slice itself:

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

As a special case, it is legal to append a string to a byte slice, like this:

slice = append([]byte("hello "), "world"...)

Upvotes: 59

peterSO
peterSO

Reputation: 166925

Appending to and copying slices

The variadic function append appends zero or more values x to s of type S, which must be a slice type, and returns the resulting slice, also of type S. The values x are passed to a parameter of type ...T where T is the element type of S and the respective parameter passing rules apply. As a special case, append also accepts a first argument assignable to type []byte with a second argument of string type followed by .... This form appends the bytes of the string.

append(s S, x ...T) S  // T is the element type of S

s0 := []int{0, 0}
s1 := append(s0, 2)        // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)  // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)    // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}

Passing arguments to ... parameters

If f is variadic with final parameter type ...T, then within the function the argument is equivalent to a parameter of type []T. At each call of f, the argument passed to the final parameter is a new slice of type []T whose successive elements are the actual arguments, which all must be assignable to the type T. The length of the slice is therefore the number of arguments bound to the final parameter and may differ for each call site.

The answer to your question is example s3 := append(s2, s0...) in the Go Programming Language Specification. For example,

s := append([]int{1, 2}, []int{3, 4}...)

Upvotes: 112

Related Questions