-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: spec: allow x, y..., z in list for variadic function argument #18605
Comments
Would probably also want to handle args to append:
And so needs to be considered together with #15209. |
Postponing to Go 2. |
I've published an experience report about the issues with Go's current behaviour regarding this: https://gist.github.com/dpinela/f7fdeb0ce3e1f0b4f495917ad9210a85 |
As mentioned above, given |
I suspect that the change in semantics would fix more call sites than it breaks: functions involving variadic arguments for anything other than the For example, I've seen a fair amount of code using variadic arguments to add a set of default options to a function that accepts variadic options: package wrapper
func Dial(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
return grpc.Dial(target, append(opts, grpc.WithFoo(foo))...)
} Those wrapper functions are subtly wrong: a caller may reasonably expect to be able to pass a subslice of options, but that pattern introduces a surprising mutation (a form of aliasing bug): package caller
[…]
opts := []grpc.DialOption{ …, grpc.WithBar(bar) }
// Dial c1 with all options except for baz.
c1, err1 := wrapper.Dial(wrapperTarget, opts[:len(opts-1)]...)
// Dial c2 with all options‽
c2, err2 := wrapper.Dial(wrapperTarget, opts...)
[…] In this snippet, the second |
I think @bcmills put it eloquently, the current optimization of reusing the slice is ripe for subtle bugs, and optimizations should only be done in cases where the compiler can prove that the programmer isn't shooting themselves in the foot. Also, to quote the original post,
Because the implementer of the function (e.g., The only alternative would be to support read only slices, which I doubt is going to be a thing any time soon(?). |
It's worth considering that if we change variadic arguments to always copy the slice, then a printf wrapper, like, say, func Printf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
} so a call to this function passing |
Another point, if we change variadic arguments to always copy the slice, I think it could be possible to make it covariant as the linear cost would be the same.
For that simple case, it's a win. Not sure about advanced use case though. |
@ianlancetaylor said:
As @blixt suggested, the compiler should probably optimize cases where the slice doesn't escape and is not modified, which fits this example. But, I want to float a very different idea: Deprecate variadic functions, make slice construction easier. Given that func F(a []int) {}
F({1, 2, 3}) On the other hand we probably want to allow construction of slices using a := []int{2, 3}
b := []int{1, a...}
F({b..., 4}) // Equivalent to F({1, 2, 3, 4}) This approach would solve a number of issues around "variadic" arguments:
I think this would actually simplify the language, while making it more expressive and flexible in many cases. Edit: a := []interface{}{2, 3}
fmt.Println({1, a...}...) (I guess that means deprecation of variadic functions is a related but separate discussion). |
@ianlancetaylor wrote:
What about |
Above in #18605 (comment) I mentioned that adopting this change would strongly suggest that we should change |
Isn't the current passing of the slice at best an optimization, and as shown above, a potential source of bugs? And due to the unknowable condition of the incoming slice, isn't this a safe change to make? Any code that expected only a copy, or only an existing slice, was already buggy. And every version of Go with bug fixes does allow the exact same code to compile without errors, but behaves differently. The spec says:
It generally implies that when possible the slice "may be passed unchanged" but it seems like there is no strong assertion that the passed on slice must not or cannot be a copy. By the way, this issue wasn't originally tagged Go2. In its original form it is possible to implement for Go 1.x. |
@ianlancetaylor could we do this only for append? Then at least the construction of the slice to pass to the variadic function would be simpler. And append is already special in all sorts of ways. |
@blixt The language in the spec is a bit confusing. The word "may" there means "you may pass the slice unchanged by following it with |
All language changes are now considered to be Go 2 issues, whether they are Go 1 compatible or not. |
@josharian Doing this just for |
I think this should also be done for prepending arguments, as the workaround for that case is arguably worse (i.e |
I would like to share a sort of counter-example here. Suppose that The knee-jerk reaction is to say the compiler should just optimize that to directly copy a := []int{1, 2}
a = append(a[:0], a[1], a[:1]...) That would result in |
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. |
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. |
(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:
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:
...
is passed on as-is (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
The text was updated successfully, but these errors were encountered: