Skip to content

proposal: spec: allow x, y..., z in list for variadic function argument #18605

Not planned
@blixt

Description

@blixt

(See the bottom of this post for a full example.) This is something I've run into a few times and I've been unable to find a discussion fully explaining why it can't/shouldn't be supported.

I've tried searching for prior discussion about this and have mostly come up with Stack Overflow questions where people have been confused about the current restriction. The best reasoning I've found is in #2873:

To avoid confusion about what slice is being passed, the ... syntax only applies when it is providing the entire ... argument; you can't splice in additional parameters with it.

I disagree with this. It implies the developer is already expected to understand that 1) ...string denotes a slice and 2) there would be two slices (i.e., your variadic arguments were turned into a slice implicitly).

They could also be expected to understand that the second slice (paths in my example below) is appended to the first slice if you were to mix the two approaches. I would even argue that is the expected behavior.

Ultimately, if Go was to support this proposal there are (AFAICT) still only two possible outcomes:

  1. The slice followed by ... is passed on as-is (0 positional arguments)
  2. A new slice is implicitly created (>0 positional arguments)

This proposal extends the second case to also implicitly append the slice followed by ... to the implicit slice (approximately what I've done as a workaround in the example below).

One new behavior resulting from this is that modifying some values in the resulting slice will have no outside effect, while modifying the indexes at the end that correspond to the passed-in slice may modify it. I don't think this is very different from today however, as there is currently no way to know if modifying any index in the argument slice will have any outside effect or not, so touching it should be strongly discouraged regardless. For such cases, an explicit slice should always be passed instead.

Full example

package main

import (
    "bytes"
    "fmt"
    "os/exec"
)

func main() {
    list("/usr/share", "/var/tmp")
}

func list(paths ...string) {
    // Desired:
    //cmd := exec.Command("ls", "-la", paths...)
    // Current workaround:
    cmd := exec.Command("ls", append([]string{"-la"}, paths...)...)
    var out bytes.Buffer
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        fmt.Printf("error: %v\n", err)
    }
    fmt.Println(out.String())
}

Activity

added this to the Proposal milestone on Jan 11, 2017
changed the title [-]proposal: allow variadic functions to implicitly append a slice to the argument slice[/-] [+]proposal: spec: allow mixing args and ...args in list for variadic function argument[/+] on Jan 23, 2017
changed the title [-]proposal: spec: allow mixing args and ...args in list for variadic function argument[/-] [+]proposal: spec: allow x, y..., z in list for variadic function argument[/+] on Jan 23, 2017
rsc

rsc commented on Jan 23, 2017

@rsc
Contributor

Would probably also want to handle args to append:

x = append(x, "x", foo..., "z")

And so needs to be considered together with #15209.

rsc

rsc commented on Mar 6, 2017

@rsc
Contributor

Postponing to Go 2.

added
v2An incompatible library change
on Mar 6, 2017
added
NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.
on Jan 23, 2018
dpinela

dpinela commented on Jan 24, 2018

@dpinela
Contributor

I've published an experience report about the issues with Go's current behaviour regarding this: https://gist.github.com/dpinela/f7fdeb0ce3e1f0b4f495917ad9210a85

22 remaining items

ianlancetaylor

ianlancetaylor commented on Feb 6, 2019

@ianlancetaylor
Contributor

@josharian Doing this just for append is #4096.

removed
NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.
on Aug 16, 2019
added
NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.
on Sep 3, 2019
lolbinarycat

lolbinarycat commented on Jul 18, 2020

@lolbinarycat

I think this should also be done for prepending arguments, as the workaround for that case is arguably worse (i.e f(append([]Type{a,b},l...)...) vs f(append(l,a,b)...) )
This is further worsened if Type has a long name and/or is in another package.
Here's what these look like with the proposal: f(a, b, l...) and f(l..., a, b).
Here's the equivalents with type inference: f({a, b, l...}) and f({l..., a, b}).
Both of these are far more readable than the current system. I think the second one would be more readable in a function call with more arguments, as it makes it clear how many arguments are required.

rittneje

rittneje commented on Oct 25, 2022

@rittneje
Contributor

I would like to share a sort of counter-example here. Suppose that append(a, b, c...) was legal, equivalent to append(a, append(b, c...)...). Based upon what is described here, it sounds like b and c would be copied to some temp slice, and then the temp slice would be appended to a, resulting in two rounds of copying, which is undesirable, especially if c is long.

The knee-jerk reaction is to say the compiler should just optimize that to directly copy b and c into a (assuming there is room). However, that would result in incorrect behavior in a situation like this:

a := []int{1, 2}
a = append(a[:0], a[1], a[:1]...)

That would result in [2 1] under the non-optimized implementation, but would end up with [2 2] under the optimized one. Thus the compiler (or runtime?) would have to somehow prove we are not in such a situation in order to avoid the extra copying.

DeedleFake

DeedleFake commented on Oct 25, 2022

@DeedleFake

It might be possible to detect a situation like that at runtime. If so, the compiler could insert a special case where it checks for that case and then dynamically falls back to doing a second copy if necessary.

ianlancetaylor

ianlancetaylor commented on Aug 23, 2023

@ianlancetaylor
Contributor

As noted above, this would either change the behavior of existing code, or it would lead to confusing special cases. I don't think we can make this change. Thanks.

locked and limited conversation to collaborators on Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeLanguageChangeSuggested changes to the Go languageNeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.Proposaldotdotdot...v2An incompatible library change

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @josharian@rsc@blixt@DeedleFake@FlorianUekermann

        Issue actions

          proposal: spec: allow x, y..., z in list for variadic function argument · Issue #18605 · golang/go