Assigning multiple return values of a single func to multiple variables?

3,835 views
Skip to first unread message

Philipp Schumann

unread,
Oct 31, 2012, 5:07:42 AM10/31/12
to golan...@googlegroups.com
See this for an example of what I mean:

http://play.golang.org/p/ivgLHovmUr

Obviously gives compiler error:

prog.go:14: assignment count mismatch: 3 = 1

I liked this kind of style in Python, given that mulltiple assignments per line are possible in Go (ie. x, y, z = 1, 2, 3), is there a syntactic technique for the above?

minux

unread,
Oct 31, 2012, 5:15:06 AM10/31/12
to Philipp Schumann, golan...@googlegroups.com
your "func GetNStrings (count int) (vals []string)" doesn't return multiple return values,
it returns only a single return value, which is a slice of string.

Francisco Souza

unread,
Oct 31, 2012, 5:16:52 AM10/31/12
to Philipp Schumann, golang-nuts
Not really. Go supports returning multiple values. If your function
returns N values, then you have to assign these values to N variables
(or ignore them).

In your case, your function return one value, then you can assign it to
one variable :)

--
~f

Job van der Zwan

unread,
Oct 31, 2012, 5:29:51 AM10/31/12
to golan...@googlegroups.com


As minux hinted at, what you're basically asking for is implicit type conversion, which Go simply does not do. Your function returns a slice, which can be of variable size, doesn't it make more sense if the program reflects this?

http://play.golang.org/p/CrvH2cfkPV

Stephen Day

unread,
Nov 1, 2012, 3:00:08 AM11/1/12
to golan...@googlegroups.com
He's actually asking for destructing assignment, which is unsupported in Go.

Job van der Zwan

unread,
Nov 1, 2012, 3:42:28 AM11/1/12
to golan...@googlegroups.com
On Thursday, 1 November 2012 08:00:08 UTC+1, Stephen Day wrote:
He's actually asking for destructing assignment, which is unsupported in Go.

Do you mean destructuring assignment? I just googled "destructing assignment" and that's what it returned instead. In either case, I had never heard of the term before (so thanks for introducing me to it), but it looks to me that even if Go did support destructuring assigment, it shouldn't support it in this case: there's no way for the compiler to know what the length of the returned slice would be, so there's no way to guess if assigning to three variables is legal or not. Unless it would something like "just assign the first three values of the slice, and if the slice is smaller assign zero", which sounds like a horrible idea to me.

minux

unread,
Nov 1, 2012, 3:51:44 AM11/1/12
to Job van der Zwan, golan...@googlegroups.com
right. if we had a tuple type, this destructuring assignment would be
possible. 

tomwilde

unread,
Nov 1, 2012, 4:21:22 AM11/1/12
to golan...@googlegroups.com
Or make it possible only if the value to be destructed is an array (with a specific length).

Rory McGuire

unread,
Nov 1, 2012, 5:03:57 AM11/1/12
to golan...@googlegroups.com, Job van der Zwan
Hi minux,

On Thursday, 1 November 2012 09:52:13 UTC+2, minux wrote:
right. if we had a tuple type, this destructuring assignment would be
possible. 

Are tuples being considered for Go?

Have you seen any reason for Go to get Tuples?
 

Kevin Gillette

unread,
Nov 1, 2012, 6:38:19 AM11/1/12
to golan...@googlegroups.com, Job van der Zwan
Tuples are essentially just arrays... or structs... depending on what you're trying to do. If you consider something like python, where you can construct a tuple of any length at runtime, then tuples are like slices. In any case, Go already has all three work-alikes, and it seems it'd break the orthogonality thrice-ways if "tuples" were added.

I don't see this "destructuring assignment" couldn't be done with slices, as something of the inverse of how variadics are handled. You extend `...` to be allowed in arbitrary single-valued expressions who's type happens to be a slice. In this case, line 14 of the original playground snippet would be changed to:

var s1, s2, s3 = GetNStrings(3)...

Behavior:

1) if the returned slice, at runtime, has less than the desired number of elements, the remaining variables get a zero value.
2) if the returned slice, at runtime, has less than the desired number of elements, panic.

In the case of there being more elements than desired, we can be more flexibile:
1) if there's a trailing blank variable, extra elements are ignored. ex:

  var s1, s2, s3, _ = GetNStrings(3)...

2) if there's no trailing blank, and there are extra elements, panic.

3) if there's a leading blank, it absorbs zero or more elements at the beginning of the slice, with variables being assigned in forward order on the tail of the slice.

The compiler would detect expressions like the above, and do size checking as needed. When not used in an assignment, intermediates could be used, such as when filling a slice into a non-variadic (currently, slices can only be filled into variadics, of course).

Anyway, since some behavior would necessarily have to be pushed into the runtime logic (though it's certainly doable with slices), either adding more spots for panics to occur, or for data to get "lost" (and thus comparatively impossible to debug), despite my believing it's doable, I don't really see the great benefit it would bring. Trying to pound a variable-length peg into a fixed-length hole _feels_ like it'll be the cause of many bugs, and will be abused by being used when the existing multi-value aspects of the language would have been an all-around cleaner solution.

Peter

unread,
Nov 1, 2012, 6:50:31 AM11/1/12
to golan...@googlegroups.com, Job van der Zwan
I agree with Kevin. I can't see how I would need this feature (I haven't so far, anyway). How often do you return a variable number of arguments and don't want a slice? Why wouldn't you want a slice? 

minux

unread,
Nov 1, 2012, 7:16:56 AM11/1/12
to Rory McGuire, golan...@googlegroups.com, Job van der Zwan
On Thu, Nov 1, 2012 at 5:03 PM, Rory McGuire <rjmc...@gmail.com> wrote:
On Thursday, 1 November 2012 09:52:13 UTC+2, minux wrote:
right. if we had a tuple type, this destructuring assignment would be
possible. 

Are tuples being considered for Go?
AFAIK, no. 

Have you seen any reason for Go to get Tuples?
none (yet).

minux

unread,
Nov 1, 2012, 7:19:09 AM11/1/12
to Peter, golan...@googlegroups.com, Job van der Zwan
On Thu, Nov 1, 2012 at 6:50 PM, Peter <peter.a...@gmail.com> wrote:
I agree with Kevin. I can't see how I would need this feature (I haven't so far, anyway). How often do you return a variable number of arguments and don't want a slice? Why wouldn't you want a slice? 
tuples are useful because it can contain different types of objects (unlike strict typing
arrays and slices).

and i think the main usage for tuples is to compensate the lack of multiple return values.

Go does have support for multiple return values, so i think one large use case of tuples
is gone. 

Michael Jones

unread,
Nov 1, 2012, 12:28:04 PM11/1/12
to minux, Peter, golan...@googlegroups.com, Job van der Zwan
I would have used this in one of my programs. I converted a core
recursion to a self managed combination of return stack and gotos for
a 34% speedup. My stack has three variables that I need to restore
before the goto on each "return." I tried many different ways of
coding the push and the pop: as an array of structures with three
values, as three arrays of values, etc. The performance in each case
is limited by either the cost of the array bounds test (either one or
three) or the cost of the packing and unpacking of the structure
(three assignments or structure assignment of a struct literal. The
three cases are:

{
sp := &exact.stack[k]
columns = sp.columns
c = sp.c
r = sp.r
}

/*
columns = exact.stack[k].columns
c = exact.stack[k].c
r = exact.stack[k].r
*/

The uncommented version is the fastest by far. The braces are needed
to help the compiler handle register pressure; the code is 2% slower
without them. What I wanted was this:

columns, c, r = destructure(exact.stack[k])

which would let the compiler do one array bounds check, one index
operation, and three direct moves. But I did not ask for it. I always
seem to be asking for things that nobody else wants. For example, I
would not put shaving a microsecond from the compile time above
forcing programmers to try an exponential number of brace
installations to manual show variable lifetimes. ;-)
> --
>
>



--
Michael T. Jones | Chief Technology Advocate | m...@google.com | +1
650-335-5765

minux

unread,
Nov 1, 2012, 12:41:59 PM11/1/12
to Michael Jones, Peter, golan...@googlegroups.com, Job van der Zwan
On Fri, Nov 2, 2012 at 12:28 AM, Michael Jones <m...@google.com> wrote:
I would have used this in one of my programs. I converted a core
recursion to a self managed combination of return stack and gotos for
a 34% speedup. My stack has three variables that I need to restore
before the goto on each "return." I tried many different ways of
coding the push and the pop: as an array of structures with three
values, as three arrays of values, etc. The performance in each case
is limited by either the cost of the array bounds test (either one or
three) or the cost of the packing and unpacking of the structure
(three assignments or structure assignment of a struct literal. The
three cases are:

        {
        sp := &exact.stack[k]
        columns = sp.columns
        c = sp.c
        r = sp.r
        }

/*
        columns = exact.stack[k].columns
        c = exact.stack[k].c
        r = exact.stack[k].r
*/

The uncommented version is the fastest by far. The braces are needed
to help the compiler handle register pressure; the code is 2% slower
Thank you for sharing this great micro optimization trick.  
without them. What I wanted was this:

     columns, c, r = destructure(exact.stack[k])

which would let the compiler do one array bounds check, one index
operation, and three direct moves. But I did not ask for it. I always
seem to be asking for things that nobody else wants. For example, I
would not put shaving a microsecond from the compile time above
forcing programmers to try an exponential number of brace
installations to manual show variable lifetimes.  ;-)
Yeah, this is a reasonable and practical use case of destructuring
assignment.

However, i think that the manual sub-expression elimination also
helps to reduce cognition load so it's fairly useful and i don't expect
the compiler's help with this.
IMO, the manual scoping to reduce registerization pressure is a work
around, and i hope the compiler can handle this automatically in the
future.

Comparing:
   columns, c, r = destructure(exact.stack[k])
and:
   sp := &exact.stack[k]
   columns, c, r = sp.columns, sp.c, sp.r

I think the latter form is quite acceptable (no error when struct fields
are reordered, thus adhere to the explicitness principle of Go)

Michael Jones

unread,
Nov 1, 2012, 12:50:24 PM11/1/12
to minux, Peter, golan...@googlegroups.com, Job van der Zwan
On Thu, Nov 1, 2012 at 9:41 AM, minux <minu...@gmail.com> wrote:

> Comparing:
> columns, c, r = destructure(exact.stack[k])
> and:
> sp := &exact.stack[k]
> columns, c, r = sp.columns, sp.c, sp.r
>
> I think the latter form is quite acceptable (no error when struct fields
> are reordered, thus adhere to the explicitness principle of Go)

True, those are the advantages. Disadvantage is being "slower than
optimal by design."

Peter

unread,
Nov 1, 2012, 12:53:59 PM11/1/12
to golan...@googlegroups.com, minux, Peter, Job van der Zwan
Is it necessarily slower?

A compiler could reasonably see that k is not changed and only do bounds checking once.

Explicit and fast are both good Go principles :)

Job van der Zwan

unread,
Nov 1, 2012, 1:30:41 PM11/1/12
to golan...@googlegroups.com, minux, Peter, Job van der Zwan
On Thursday, 1 November 2012 17:28:51 UTC+1, Michael Jones wrote:
        { 

        sp := &exact.stack[k] 
        columns = sp.columns 
        c = sp.c 
        r = sp.r 
        } 
Wow, I never would have thought of doing something like that (including the manual scoping). Do you have a blog or a summary page where you collect micro-optimisations like this? Preferably with the reasoning that made you discover them in the first place? It seems to be kind of your thing.

On Thursday, 1 November 2012 17:53:59 UTC+1, Peter wrote:
Is it necessarily slower?

A compiler could reasonably see that k is not changed and only do bounds checking once.

Explicit and fast are both good Go principles :)

Do any of the Go compilers do any optimisations that involve avoiding unnecessary bounds checking? And what do you consider reasonable? I wouldn't expect this to be optimised:

columns = exact.stack[k].columns
c = exact.stack[k].c
r = exact.stack[k].r

But this seems a bit more likely to me:

columns, c, r = stack[k].columns, stack[k].c, stack[k].r

... since I think the evaluation of the left and right side could reasonably be micro-optimised.

minux

unread,
Nov 1, 2012, 1:58:01 PM11/1/12
to Job van der Zwan, golan...@googlegroups.com, Peter

On Fri, Nov 2, 2012 at 1:30 AM, Job van der Zwan <j.l.van...@gmail.com> wrote:
Do any of the Go compilers do any optimisations that involve avoiding unnecessary bounds checking? And what do you consider reasonable? I wouldn't expect this to be optimised:

columns = exact.stack[k].columns
c = exact.stack[k].c
r = exact.stack[k].r

But this seems a bit more likely to me:

columns, c, r = stack[k].columns, stack[k].c, stack[k].r

... since I think the evaluation of the left and right side could reasonably be micro-optimised.
Russ once said that he didn't like the idea of common sub-expression elimination in the gc compiler,
and i tend to agree with that for this case, it's better to manually factor out the common parts, because
it will make the intentions more clear.

of course, it'd be nice to have the compiler avoid redundant bound checks in this case.

Michael Jones

unread,
Nov 1, 2012, 8:06:01 PM11/1/12
to Job van der Zwan, golan...@googlegroups.com, minux, Peter
comments inline...

On Thu, Nov 1, 2012 at 10:30 AM, Job van der Zwan <j.l.van...@gmail.com> wrote:
On Thursday, 1 November 2012 17:28:51 UTC+1, Michael Jones wrote:
        { 

        sp := &exact.stack[k] 
        columns = sp.columns 
        c = sp.c 
        r = sp.r 
        } 
Wow, I never would have thought of doing something like that (including the manual scoping). Do you have a blog or a summary page where you collect micro-optimisations like this? Preferably with the reasoning that made you discover them in the first place? It seems to be kind of your thing.

No. This is not something to "share" really as much as something to prod the compiler writers with to allow a little bit more of the goodness from IBM's Fortran-H compiler and the 50 years of research since into their work. I have a different philosophy about this than they do, but I'm not working on the compilers. ;-)

I think that a compiler is the tool to help me squeeze every picosecond of needless expense. I have some research ideas of my own here but no agenda to push. I just value the whole spectrum from simple and concise on the human side of the tool chain to time and space efficiency on the machine side.

The way I thought of the braces was to add the code above sans-braces and watch the performance drop by half, look at the generated code/profile, see that a dreaded stack split has fallen where I can't afford it, and then try to help the compiler realize shorter variable lifetimes with extra scoping crutches. I do not want people to do this! I want the compiler to traverse the expression tree BOTTOM UP and realize what can be reused. These are early days yet and the compilers should get much better.
 

On Thursday, 1 November 2012 17:53:59 UTC+1, Peter wrote:
Is it necessarily slower?

A compiler could reasonably see that k is not changed and only do bounds checking once.

Explicit and fast are both good Go principles :)

Do any of the Go compilers do any optimisations that involve avoiding unnecessary bounds checking? And what do you consider reasonable? I wouldn't expect this to be optimised:

columns = exact.stack[k].columns
c = exact.stack[k].c
r = exact.stack[k].r

If I gave you...

columns = exact.stack[k].columns
c = exact.stack[k].c
r = exact.stack[k].r

...and paid you to write it in assembler, I would expect you to compute the address of exact.stack once, exact.stack[k] once, and do a range-check once. If you did not, after representing yourself as a skilled coder with deep knowledge of the machine, I would fire you. That's how I think of compilers. They are the machine experts. They alone have the whole problem before them (the user's code, the code "inside" for loops and switch statements, the bounds checking code, the code for maps, etc.) They also have the result of developer code and generated code, such as from M4 or setter/getter automation. With all of this before them, and knowledge of the machine, and much more, they [well, it] are in the best position to know what is a common expression, what can be safely cached, what is less expensive to recompute, if x*2 or x<<1 is faster, and much more. I think this is the job.
 
But this seems a bit more likely to me:

columns, c, r = stack[k].columns, stack[k].c, stack[k].r

... since I think the evaluation of the left and right side could reasonably be micro-optimised

Is not yet, but the future is limited only by vision.

P.S. The compiler writers know how to do this. They are just being careful to keep all the code as clean and understandable as they can. Think "linux v1." Magic here can happen later. I'm just impatient.

Stephen Day

unread,
Nov 1, 2012, 10:08:09 PM11/1/12
to golan...@googlegroups.com
Yes, I meant "destructuring assignment". Typo.

Thomas Bushnell, BSG

unread,
Nov 2, 2012, 3:51:13 AM11/2/12
to Michael Jones, Peter, minux, golan...@googlegroups.com, Job van der Zwan

I expect gccgo will do the right thing here.

--
 
 
Reply all
Reply to author
Forward
0 new messages