Search code examples
gogolangci-lint

What is the idiomatic method and style of combining two slices in go?


When combining two slices, e.g. fruit and vegetables, to create a third, e.g. food. it would seem to make sense to append fruit and vegetable and assign the result directly to food.
golangci-lint's check: "appendAssign" suggests that is not an accepted way of doing so, with an alternative (see below) being preferrable.

What is the most idiomatic way of achieving this?

Code to illustrate question: https://go.dev/play/p/IFV6o92-HTb

package main

import "fmt"

func main() {
    // fails linting with: gocritic: appendAssign: append result not assigned to the same slice
    fruits := []string{"banana", "strawberry"}
    vegetables := []string{"potato", "carrot"}

    food := append(fruits, vegetables...)

    fmt.Println(food)

    // reset
    food = nil

    // passes linting
    food = fruits
    food = append(food, vegetables...)

    fmt.Println(food)
}

Solution

  • One reason might be that you can get surprising results, like (Go Playground):

    func main() {
        fruits := []string{"banana", "strawberry", "apple", "cherry"}
        vegetables := []string{"potato", "carrot"}
    
        food := append(fruits[:2], vegetables...)
    
        fmt.Println(food) // [banana strawberry potato carrot]
        fmt.Println(fruits) // [banana strawberry potato carrot]
    }
    

    or even

    func main() {
        allFruits := []string{"banana", "strawberry", "apple", "cherry"}
        fruits := allFruits[:2]
        vegetables := []string{"potato", "carrot"}
    
        food := append(fruits, vegetables...)
    
        fmt.Println(food) // [banana strawberry potato carrot]
        fmt.Println(allFruits) // [banana strawberry potato carrot]
    }
    

    because you potentially modify the underlying array of the first slice, which could be easily missed by the casual reader. It is immediately clear when you assign to the same variable in the same step.

    The best way would be to use slices.Concat:

        food := slices.Concat(fruits, vegetables)
    

    an alternative would be:

        food := make([]string, 0, len(fruits)+len(vegetables))
        food = append(food, fruits...)
        food = append(food, vegetables...)
    

    having the advantage of an preallocated array. Also possible is:

        var food []string
        food = append(food, fruits...)
        food = append(food, vegetables...)
    

    While what you are doing is fine from a language perspective, it could be surprising for people quickly reading over your code.

    You are free to disagree and ignore the linter warning or turn it off. As a matter of opinion I think the alternative approaches are much more readable, especially slices.Concat.