What does ... mean when coming directly after a slice?
Asked Answered
Y

2

14

What does ... mean in this context in Go?

ids = append(ids[:index], ids[index+1:]...)

I have read this great Q&A: Do three dots (which is called wildcard?) contain multiple meanings?

about what ... means in some situations, but I don't understand what it means in the situation above.

Yogh answered 1/6, 2016 at 13:49 Comment(3)
The three dots is called an "ellipsis". The question you linked directly answers you question. The other answer is kind of needlessly wordy, what what about it don't you understand?Wohlert
TL;DR: assume a := []int{1,2,3} -- then do(a...) is equivalent to do(1, 2, 3)Pulpboard
Possible duplicate of dot dot dot in golang. interface with empty bracesValue
R
22

Some languages (C, Python, ...) accept variadic arguments. Basically, you allow the client of a function to pass a number of arguments, without specifying how many. Since the function will still need to process these arguments one way or another, they are usually converted to a collection of some sort. For instance, in Python:

def foo(x, *args): # * is used for variadic arguments
    return len(args)

>>> foo(1)  # Passed the required argument, but no varargs
0 
>>> foo(1, 2, 3)  # Passed the required, plus two variadic arguments
2
>>> foo(1, 2, 3, 4, 5, 6) # required + 5 args, etc...
5

Now one obvious problem of that approach is that a number of arguments is quite a fuzzy concept as far as types are concerned. C uses pointers, Python doesn't really care about types that much in the first place, and Go takes the decision of restricting it to a specified case: you pass a slice of a given type.

This is nice because it lets the type-system do its thing, while still being quite flexible (in particular, the type in question can be an interface, so you can pass different 'actual types' as long as the function knows how to process these.

The typical example would be the Command function, which executes a program, passing it some arguments:

func Command(name string, arg ...string) *Cmd

It makes a lot of sense here, but recall that variadic arguments are just a convenient way to pass slices. You could have the exact same API with:

func Command(name string, args []string) *Cmd

The only advantage of the first is that it lets you pass no argument, one argument, several... without having to build the slice yourself.

What about your question then ?

Sometimes, you do have a slice, but need to call a variadic function. But if you do it naively:

my_args_slice := []string{"foo", "bar"}
cmd := Command("myprogram", my_args_slice)

The compiler will complain that it expects string(s), but got a slice instead ! What you want to tell it is that it doesn't have to 'build the slice in the back', because you have a slice already. You do that by using this ellipsis:

my_args_slice := []string{"foo", "bar"}
cmd := Command("myprogram", my_args_slice...) // <- Interpret that as strings

The append function, despite being built-in and special, follows the same rules. You can append zero, one, or more elements to the slice. If you want to concatenate slices (i.e. you have a 'slice of arguments' already), you similarly use the ellipsis to make it use it directly.

Rapping answered 1/6, 2016 at 14:27 Comment(1)
Thank you! Very well explainedYogh
S
3

It unpacks slice.

ids[:index] is a short form of ids[0:index]

ids[index+1:] is a short form of ids[index+1:len(ids)-1]

So, your example ids = append(ids[:index], ids[index+1:]...) translates to

//pseudocode
ids = append(ids[0:index], ids[index+1], ids[index+2], ids[index+3], ..., ids[len(ids)-2], ids[len(ids)-1])
Subito answered 1/6, 2016 at 14:4 Comment(7)
You could think about it that way, but it reality it assigns the slice directly to the variadic slice in the arguments, which is much more efficient.Wohlert
It's not "unpacking". Rather, it's close to "bypassing". That pseudocode is not equivalent to using ...(ellipsis). As @Wohlert commented, if they are equivalent, a new slice with a different array should be made, but it's not. The slice inside of the append function points to the same underlying array.Foolery
@starriet why should it be different array? go.dev/play/p/IbdYeAJD1su different slice, same underlying arraySubito
@Subito Great example, but it's not related to what I mentioned above. What I'm saying is the parameter within the variadic function(append, in this case), not the returned slice of append. In your playground code, the first append creates a new slice with a new underlying array and then copies the elements to the array of the 1st input slice. But the second append does not create a new array. It just uses the existing underlying array and copies the elements to the array of the 1st input slice. So, these two returned slices look the same but the function works differently.Foolery
@starriet no, it is not. check the output. its the same [1 2 3 5 6 7 8 9 9] [1 2 3 5 6 7 8 9] (4 is missing in original a slice) for both cases meaning 1st example does not create new underlying array. If it was the case output would be [1 2 3 4 5 6 7 8 9] [1 2 3 5 6 7 8 9] (4 is still there). Both examples works the same way and have nothing to do with semantics, only len and cap of "original" slice and number of elements to append matters.Subito
@Subito Yes, I totally understand what your code is trying to show(I think it's a very good example to understand slice). But, again, that's not related to what I'm talking about. You're talking about the return value. I'm talking about the parameter within the variadic function. Think about it: what are you going to do inside of the variadic function 1) if you got multiple inputs. 2) if you got a slice(this is the case when ... is used). I'm talking about "under the hood" stuff :)Foolery
@Subito this code example will clarify everything. You will easily understand why it is not "unpacking". Please check it out. P.S. I didn't downvote your answer. All these answers and comments should be respected. Enjoyed the discussion!Foolery

© 2022 - 2024 — McMap. All rights reserved.