Re: [go-nuts] Proper return statement for a struct in a function

11,599 views
Skip to first unread message

Jesse McNelis

unread,
Sep 29, 2012, 3:08:27 AM9/29/12
to Ji H Park, golan...@googlegroups.com
On Sat, Sep 29, 2012 at 3:17 PM, Ji H Park <jhp...@gmail.com> wrote:
> What would be the proper value to return for the structs when an exception
> occurs?

The zero value of the struct.

func testreturn() (Item, error) {
return Item{}, errors.New("error")
}

--
=====================
http://jessta.id.au

stevewang

unread,
Sep 29, 2012, 4:52:02 AM9/29/12
to golan...@googlegroups.com
I think the underlying question is what to indicate an invalid object, so the answer is pointer.

func testreturn() (*Item, error) {
return nil, errors.New("error")
}

On Saturday, September 29, 2012 1:17:30 PM UTC+8, Ji H Park wrote:
Hello there, I'm trying to figure out a way to properly return a type of nil value but for a struct in a function.

I have a function that returns a struct and error types.

When it runs into an exception, I thought I could use "nil" as the return value for the struct, but the following error keeps coming up:

"cannot use nil as type Item in return argument"

What would be the proper value to return for the structs when an exception occurs?

I'd appreciate your help! thanks!

Here's the code:

func testreturn() (Item, error) {
return nil, errors.New("error")
}

Jesse McNelis

unread,
Sep 29, 2012, 4:58:24 AM9/29/12
to stevewang, golan...@googlegroups.com
On Sat, Sep 29, 2012 at 6:52 PM, stevewang <steve....@gmail.com> wrote:
> I think the underlying question is what to indicate an invalid object, so
> the answer is pointer.

The error not being nil indicates that the Item is invalid.
nil is a perfectly valid value for a pointer in Go. One shouldn't
consider a nil pointer to be invalid any more than a zero struct
value.

--
=====================
http://jessta.id.au
Message has been deleted

stevewang

unread,
Sep 29, 2012, 5:11:35 AM9/29/12
to golan...@googlegroups.com
I agree with you that it's a must to return an error to indicate an error and the caller is responsible for checking the returned error.
I just meant that nil is more suitable than zero object, because zero object is accessible but nil can't be dereferenced.

Michael Jones

unread,
Sep 29, 2012, 5:13:56 AM9/29/12
to stevewang, golan...@googlegroups.com
Out of band signaling of errors (say, through an extra error argument) is a good thing.

--
 
 



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

stevewang

unread,
Sep 29, 2012, 5:55:30 AM9/29/12
to golan...@googlegroups.com
Here are two possible scenarios:

if the function return a nil:
func testreturn() (*Item, error) {
return nil, errors.New("error")
}
func anotherfun() {
  item, _ := testreturn()
  // the programmer is too lazy to check error
  // program crash here and everyone knows that there is something wrong
  item.fun()
}

but if the function return a zeor object:
func testreturn() (Item, error) {
return Item{}, errors.New("error")
}
func anotherfun() {
  item, _ := testreturn()
  // the programmer is too lazy to check error
  // nothing happens here even in case of an error
  item.fun()
}

minux

unread,
Sep 29, 2012, 9:18:51 AM9/29/12
to stevewang, golan...@googlegroups.com
On Sat, Sep 29, 2012 at 5:55 PM, stevewang <steve....@gmail.com> wrote:
Here are two possible scenarios:

if the function return a nil:
func testreturn() (*Item, error) {
return nil, errors.New("error")
}
func anotherfun() {
  item, _ := testreturn()
  // the programmer is too lazy to check error
  // program crash here and everyone knows that there is something wrong
  item.fun()
just to reiterate that calling method on a nil object pointer doesn't necessarily crash,
this behavior is quite different from other languages (like C++)
}
Message has been deleted
Message has been deleted

stevewang

unread,
Sep 29, 2012, 10:03:52 AM9/29/12
to golan...@googlegroups.com
Not necessarily, but most likely.
It depends on whether or not the function accessess its data member, both for go and C++.
 

Ji H Park

unread,
Sep 30, 2012, 12:41:44 AM9/30/12
to golan...@googlegroups.com, Ji H Park, jes...@jessta.id.au
Thank you for the help!

I've implemented this and it's working as expected!

Ji H Park

unread,
Sep 30, 2012, 12:49:31 AM9/30/12
to golan...@googlegroups.com
I've never had guessed there are two scenarios for this!

So it seems that passing a pointer-struct as a parameter and returning a nil could be more suitable choice!

SCov

unread,
Sep 30, 2012, 5:50:58 AM9/30/12
to golan...@googlegroups.com
Basically, you were trying to treat an Item value as a pointer an Item. The first one cannot take nil for value, the second one can. 


On Saturday, September 29, 2012 8:17:30 AM UTC+3, Ji H Park wrote:
Hello there, I'm trying to figure out a way to properly return a type of nil value but for a struct in a function.

I have a function that returns a struct and error types.

When it runs into an exception, I thought I could use "nil" as the return value for the struct, but the following error keeps coming up:

"cannot use nil as type Item in return argument"

What would be the proper value to return for the structs when an exception occurs?

I'd appreciate your help! thanks!

Here's the code:

func testreturn() (Item, error) {
return nil, errors.New("error")
}

SCov

unread,
Sep 30, 2012, 5:52:05 AM9/30/12
to golan...@googlegroups.com
Sorry, "[...]as a pointer TO an Item".

Ji H Park

unread,
Sep 30, 2012, 5:10:54 PM9/30/12
to golan...@googlegroups.com
I see, I'm sort of a beginner... I'm still trying to get through this Go language (which I think it's awesome by the way!).

Kevin Gillette

unread,
Oct 1, 2012, 12:30:01 AM10/1/12
to golan...@googlegroups.com
I disagree with changing the return type from a struct value to a struct pointer just to have a convenient second indicator (nil) when there is an error.

1) Use the return type that makes sense when there is no error: if value copying makes sense on the struct level, please don't use a pointer. Would you use *byte just to make it nil when there's an error?

2) Anyone calling a function that returns an error should be checking the error before anything else. If they're not, then as a library writer, you're not responsible for the consequences. Programmers absolutely should not be checking for the zero value of the main return type to determine if there was an "error" condition.

Some good exceptions to #2 are functions like strings.Index, because not finding the string is not an "error" -- it's a legitimate response to the question of "what is the index of my substring?" (in this case, the answer is "none").

Rasmus Schultz

unread,
Dec 23, 2013, 7:52:01 PM12/23/13
to golan...@googlegroups.com
I agree with all of that, but I'm stuck with the following:

func (q *NodeQueue) Pop() Node {
    if item := q.Queue.Pop(); item != nil {
        return item.(Node)
    }
    return nil
}

I'm not allowed to return nil.

So my only options are, change the method signature to return *Node, which I don't want, for the reasons you explained - or construct and return a "dummy" Node instance whenever there's nothing to return? That seems awfully wasteful.

So I tried this:

const EmptyNode = Node{}

func (q *NodeQueue) Pop() Node {
    if item := q.Queue.Pop(); item != nil {
        return item.(Node)
    }
    return EmptyNode
}

Doesn't work either.

So my best (only?) remaining option is this:

var EmptyNode = Node{}
func (q *NodeQueue) Pop() Node {
    if item := q.Queue.Pop(); item != nil {
        return item.(Node)
    }
    return EmptyNode
}

It doesn't taste right - the EmptyNode really should not be a variable.

For that matter, it shouldn't really exist at all - and yeah, I know, I still need to add an error value to the return statements, but... returning a mutable instance of Node, when in actuality no Node is available - it seems misleading at best, but really, it just seems... wrong.

There's no better way to solve this problem? I mean, this must be a very, very common problem?

Rasmus Schultz

unread,
Dec 23, 2013, 8:02:32 PM12/23/13
to golan...@googlegroups.com
No wait, I think I got it...! :-)

I should add a CanPop() method instead, right?

The consumer code will have better/simpler semantics then: "if queue can pop then pop else..."

Is that "the Go way"? :-)

Rasmus Schultz

unread,
Dec 23, 2013, 8:04:13 PM12/23/13
to golan...@googlegroups.com
Man, learning Go is just like this, all the time, isn't it?

You try 7 dumb things you would have done in other languages, and then finally Go whips you into shape ;-)

Caleb Spare

unread,
Dec 23, 2013, 8:03:41 PM12/23/13
to Rasmus Schultz, golan...@googlegroups.com
On Mon, Dec 23, 2013 at 6:52 PM, Rasmus Schultz <ras...@mindplay.dk> wrote:
I agree with all of that, but I'm stuck with the following:

func (q *NodeQueue) Pop() Node {
    if item := q.Queue.Pop(); item != nil {
        return item.(Node)
    }
    return nil
}

I'm not allowed to return nil.

So my only options are, change the method signature to return *Node, which I don't want, for the reasons you explained - or construct and return a "dummy" Node instance whenever there's nothing to return? That seems awfully wasteful.

So I tried this:

const EmptyNode = Node{}

func (q *NodeQueue) Pop() Node {
    if item := q.Queue.Pop(); item != nil {
        return item.(Node)

    }
    return EmptyNode
}

Doesn't work either.


So my best (only?) remaining option is this:

var EmptyNode = Node{}
func (q *NodeQueue) Pop() Node {
    if item := q.Queue.Pop(); item != nil {
        return item.(Node)

    }
    return EmptyNode
}

It doesn't taste right - the EmptyNode really should not be a variable.
Don't worry so much about making this a var. This is fairly common for types that cannot be consts. For instance, it's used for errors all over:

 
For that matter, it shouldn't really exist at all - and yeah, I know, I still need to add an error value to the return statements, but... returning a mutable instance of Node, when in actuality no Node is available - it seems misleading at best, but really, it just seems... wrong.

There's no better way to solve this problem? I mean, this must be a very, very common problem?
Another way to do it would be to use a named return value. http://play.golang.org/p/Z9WuVIk1zQ 


On Monday, October 1, 2012 12:30:01 AM UTC-4, Kevin Gillette wrote:
I disagree with changing the return type from a struct value to a struct pointer just to have a convenient second indicator (nil) when there is an error.

1) Use the return type that makes sense when there is no error: if value copying makes sense on the struct level, please don't use a pointer. Would you use *byte just to make it nil when there's an error?

2) Anyone calling a function that returns an error should be checking the error before anything else. If they're not, then as a library writer, you're not responsible for the consequences. Programmers absolutely should not be checking for the zero value of the main return type to determine if there was an "error" condition.

Some good exceptions to #2 are functions like strings.Index, because not finding the string is not an "error" -- it's a legitimate response to the question of "what is the index of my substring?" (in this case, the answer is "none").

On Saturday, September 29, 2012 2:52:02 AM UTC-6, stevewang wrote:
I think the underlying question is what to indicate an invalid object, so the answer is pointer.

func testreturn() (*Item, error) {
return nil, errors.New("error")
}

On Saturday, September 29, 2012 1:17:30 PM UTC+8, Ji H Park wrote:
Hello there, I'm trying to figure out a way to properly return a type of nil value but for a struct in a function.

I have a function that returns a struct and error types.

When it runs into an exception, I thought I could use "nil" as the return value for the struct, but the following error keeps coming up:

"cannot use nil as type Item in return argument"

What would be the proper value to return for the structs when an exception occurs?

I'd appreciate your help! thanks!

Here's the code:

func testreturn() (Item, error) {
return nil, errors.New("error")
}

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Jesse McNelis

unread,
Dec 23, 2013, 8:07:21 PM12/23/13
to Rasmus Schultz, golang-nuts
On Tue, Dec 24, 2013 at 11:52 AM, Rasmus Schultz <ras...@mindplay.dk> wrote:
So my only options are, change the method signature to return *Node, which I don't want, for the reasons you explained - or construct and return a "dummy" Node instance whenever there's nothing to return? That seems awfully wasteful.
From you're previous queue thread I assume Node is
type Node struct {
Value int
}

Which is the same size as the nil pointer you'd be returning anyway.
Copying 8 bytes of zeros to the stack is an immeasurably tiny amount of work.


-- 
=====================
http://jessta.id.au

Rasmus Schultz

unread,
Dec 23, 2013, 8:24:56 PM12/23/13
to Jesse McNelis, golang-nuts
The Node type in this example is just a dummy, it's going to get replaced by something much larger.

Rasmus Schultz

unread,
Dec 23, 2013, 8:26:23 PM12/23/13
to Caleb Spare, golan...@googlegroups.com
Another way to do it would be to use a named return value. http://play.golang.org/p/Z9WuVIk1zQ 

Oh, that's interesting - but still results in an empty default Node instance being constructed, doesn't it?

Caleb Spare

unread,
Dec 23, 2013, 8:41:29 PM12/23/13
to Rasmus Schultz, golan...@googlegroups.com
Yes, it does.

Please don't start caring about that until (a) you have working code that is too slow/memory-intensive for your needs and (b) you have benchmarks and profiles that prove that passing around the whole struct is the bottleneck.

Caleb Spare

unread,
Dec 23, 2013, 8:43:16 PM12/23/13
to Rasmus Schultz, golan...@googlegroups.com
(Although, after briefly glancing at your larger code sample, it does look like the kind of thing where I'd expect to see a *Node passed around, rather than a Node.)

Rasmus Schultz

unread,
Dec 23, 2013, 9:09:48 PM12/23/13
to Caleb Spare, golan...@googlegroups.com
Please don't start caring about that until (a) you have working code that is too slow/memory-intensive for your needs and (b) you have benchmarks and profiles that prove that passing around the whole struct is the bottleneck.

Performance is not my first concern - I'm not trying to optimize, I just would prefer not to return meaningless dummy values, as this can cause bugs to trickle out and makes things harder to debug... I like the addition of CanPop() which makes the consumer code more semantic and guarantees early failure.

Here's the complete code:


> it does look like the kind of thing where I'd expect to see a *Node passed around, rather than a Node

Yes, it does - it should... I still seem to get confused whenever I start juggling pointers and methods etc... I'll get there :-)

Caleb Spare

unread,
Dec 23, 2013, 9:20:44 PM12/23/13
to Rasmus Schultz, golan...@googlegroups.com
On Mon, Dec 23, 2013 at 8:09 PM, Rasmus Schultz <ras...@mindplay.dk> wrote:
Please don't start caring about that until (a) you have working code that is too slow/memory-intensive for your needs and (b) you have benchmarks and profiles that prove that passing around the whole struct is the bottleneck.

Performance is not my first concern - I'm not trying to optimize, I just would prefer not to return meaningless dummy values, as this can cause bugs to trickle out and makes things harder to debug... I like the addition of CanPop() which makes the consumer code more semantic and guarantees early failure.

In this case, I think returning a *Node and returning nil when there are no items available is fine. At least, that's what container/list does:

Dan Kortschak

unread,
Dec 23, 2013, 11:15:57 PM12/23/13
to Rasmus Schultz, golan...@googlegroups.com
Why not add a Len() int method?

egon

unread,
Dec 24, 2013, 6:20:42 AM12/24/13
to golan...@googlegroups.com
On Tuesday, December 24, 2013 2:52:01 AM UTC+2, Rasmus Schultz wrote:
> I agree with all of that, but I'm stuck with the following:
>
>
> func (q *NodeQueue) Pop() Node {
>     if item := q.Queue.Pop(); item != nil {
>         return item.(Node)
>     }
>     return nil
> }
> I'm not allowed to return nil.

Simply return (node Node, ok bool).

Jesper Louis Andersen

unread,
Dec 24, 2013, 7:32:51 AM12/24/13
to egon, golang-nuts

On Tue, Dec 24, 2013 at 12:20 PM, egon <egon...@gmail.com> wrote:
Simply return (node Node, ok bool).

This would be my approach as well. It coalesces the CanPop() call into the Pop itself:

n, empty := q.Pop()
if empty {
    ...
} else {
    n.Foo()
}

I would probably allow n to be nil. and "empty" could be an err which you can then handle explicitly. Better yet would be to have what is commonly called "Algebraic Datatypes" in ML, Haskell or Erlang:

case queue:out(Q) of
    empty -> ...;
    {val, E, Q2} -> ...
end.

but Go does not have those, sadly, so you have to handle these kinds of things in a low level way which doesn't encode the domain correctly.

--
J.

Rasmus Schultz

unread,
Dec 24, 2013, 8:23:02 AM12/24/13
to Jesper Louis Andersen, egon, golang-nuts
Yeah, I think returning (*Node, bool) should be the final solution - and it has to return *Node rather than Node, so I can return nil on error.

The bool is redundant in a sense, since *Node == nil would accomplish the same thing, but I like the bool for two reasons: it makes the consumer aware that the function might return nothing, and the symbol "ok" makes the consumer code more semantic.

Thanks for your input everyone :-)



--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/4yBldwsjZwo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

andrey....@gmail.com

unread,
Dec 24, 2013, 10:26:35 AM12/24/13
to golan...@googlegroups.com, Caleb Spare
Then may be you should create an interface? Because anyone still can create meaningless Node instance just typing

dummy := &yourpackage.Node{}

Something like this: http://play.golang.org/p/NXNFeYZLrE

вторник, 24 декабря 2013 г., 6:09:48 UTC+4 пользователь Rasmus Schultz написал:

Kevin Gillette

unread,
Dec 24, 2013, 4:49:53 PM12/24/13
to golan...@googlegroups.com, Caleb Spare, andrey....@gmail.com
On Tuesday, December 24, 2013 8:26:35 AM UTC-7, andrey....@gmail.com wrote:
Then may be you should create an interface? Because anyone still can create meaningless Node instance just typing

dummy := &yourpackage.Node{}

Expressions currently aren't addressable.

Kevin Gillette

unread,
Dec 24, 2013, 4:50:20 PM12/24/13
to golan...@googlegroups.com, Caleb Spare, andrey....@gmail.com
Nevermind, I read those as parentheses, not braces.

Kevin Gillette

unread,
Dec 24, 2013, 5:27:37 PM12/24/13
to golan...@googlegroups.com
On Monday, December 23, 2013 5:52:01 PM UTC-7, Rasmus Schultz wrote:
var EmptyNode = Node{}
It doesn't taste right - the EmptyNode really should not be a variable.

Regarding the 'taste': don't worry as much about philosophical matters -- Go is very much a 'get things done' language, which is somewhat evident by the language breaking with a couple decades of what many academicians assume are fundamental to any kind of productivity.

On a practical note, while there's nothing wrong with using global variables as sentinels, as it could be argued is happening here, just make sure that either the variable, or its type, is unexported (and if the latter, make sure no function or method can create a distinct value of that type), otherwise any other package will be able to change this package' concept of an empty node at runtime.

Also, as you noticed, Node{} is shorter than EmptyNode; it may feel "off", and it really would be, if not for Go's emphasis on zero-values being intuitive and meaningful. Because of this, gophers rightly expect that, unless documented otherwise, Node{} is an empty node, and it's common to see empty composite literals floating around code. If you really don't like the empty braces, you can just use named return values, so that when there's an error or empty case, you don't need to return use a literal or a global to get an empty Node returned.

returning a mutable instance of Node, when in actuality no Node is available - it seems misleading at best, but really, it just seems... wrong.

When paired with an error return, it's well accepted and well known convention that if the error value is non-nil, then unless documented otherwise the library function should ensure that all other returned values are their respective types' zero values, and the caller should be ignoring the other returned values. Notable exceptions include Read and Write methods (which are well documented).

Beyond convention, you can just make sure that Node has no exported fields, so that it can't be modified directly by another package. You can also make your methods panic on attempts to mutate an empty node. Of course, for any changes to persist, those methods would have to be defined on a pointer receiver, or would have to return a Node with the changes applied.
 
There's no better way to solve this problem? I mean, this must be a very, very common problem?

I wouldn't say it's a major problem, since you already discovered several solutions on your own. It may be a conceptual annoyance to some programmers.

Jsor

unread,
Dec 24, 2013, 7:12:01 PM12/24/13
to golan...@googlegroups.com, Jesper Louis Andersen, egon
As much as I don't like panicky code being exposed to the user, one exception I always make is stack/queue/deque implementations. I always use an IsEmpty() bool method and panic if Pop(/Dequeue/Poll/whatever) is called on an empty stack/queue. To me it just feels like asking for a[0] in an empty slice, I'm not sure what exactly you're trying to accomplish, but it's pretty wrong to ask an empty stack for its data.

This is purely philosophical, though, I don't think there's anything inherently wrong with rolling an IsEmpty query into Pop, or having non-panicky error behavior, but stack/queue/etc is where I tend to make an exception to my "never panic when you can error" rule. I can't really give a great argument for

for !stack.IsEmpty {

}

over

for node,ok := stack.Pop(); ok; node,ok = stack.Pop() {

}

though, so I think it's just preference.

Rasmus Schultz

unread,
Dec 24, 2013, 7:56:06 PM12/24/13
to Kevin Gillette, golan...@googlegroups.com
Kevin,

This is not (at all) about brevity or empty braces for me - it's a simple matter of code (and APIs) that make sense. (to me.)

Node{} for all intents and purposes is a null object - I have had very poor results with null objects in several languages. I find that it rarely actually helps with any of the problems you were hoping it would solve, and worst case, creates new problems and edge cases where you have to perform (later) checks to assess whether something is e.g. Node{} or actually a real, valid Node - and when you forget that these conditions can occur, or neglect to handle them in the right place, errors "bleed out" from where they actually occurred, into other areas of the program, often making it very difficult and time-consuming to find the source of the error.

A function that can't really provide a Node, but returns a Node anyway, is essentially just telling a lie - I like APIs that don't attempt to shelter you from the truth: this function may not always be able to return a Node... in this case, if the consumer of the API has not understood what a queue is, it's better to teach them the lesson as soon as possible, rather than letting them build a bunch of buggy code on a misunderstood concept - it may run, but it's not going to work. It doesn't make things fool proof or fail safe, it just makes a fool out of the consumer and makes the code fail later ;-)

This is all a matter of taste of course, and just my own personal experience and preference.

But yes, I found at least a couple of very satisfying solutions to the problem - both simpler and more elegant (I think) than what I was trying to do, and since those approaches were much simpler to implement in Go, it feels right :-)



--

Jsor

unread,
Dec 24, 2013, 9:13:40 PM12/24/13
to golan...@googlegroups.com, Kevin Gillette
You can make precisely the same arguments about null pointers, though. A null pointer is just the zero value of a pointer, Pointer{} if you will. I agree null objects become a significant problem in some languages, but that's generally because of lack of multiple return values. It's certainly a problem in C where you have to bend over backwards to ensure that structs have some error value, or else you set some error global you can get with myapiGetError() or some other such nonsense. I've definitely encountered the "error leaking" you describe (though personally I've had more problems in that vein with NaN and Inf than with structs or objects). But with Go zero objects make perfect sense if you provide a sensible error return argument, whether that be a bool, error, or otherwise.

Okay, maybe in your stack *Node = nil as an error makes sense, but it's not universally true. There's a reason maps return an ok value; nil can be a valid pointer value to store in a map (and I've explicitly stored a nil before). Returning a Node{} is in no way fundamentally different from returning a zero address -- except in cases where Node{} can have meaning and nil can't. But there are conceivable cases where Node{} is not a valid value and nil is, though we can argue about whether this scenario is more or less common. There's no good a priori reason why "forgetting" to check an error value should cause more long-term confusing butterfly-effect problems if you return a null object than if you return a null pointer.

I really feel like "A function that can't really provide a Node, but returns a Node anyway, is essentially just telling a lie" could just as easily be rephrased "A function that can't really provide a pointer to a valid address, but returns a pointer anyway, is essentially just telling a lie." I think you're just used to the conventions and pitfalls of languages that have different error handling mechanisms, or different community conventions, and are mistaking those for what is necessarily good convention in all languages. There's nothing wrong with those styles in those languages, and in the majority of cases they certainly developed for a reason, but they also didn't have Go's features to provide extra information in order to easily encourage disambiguation of zero values and valid values.

(And sorry, on reread this sounds a bit belligerent. I didn't intend it that way. Tone in text and all that)

Jsor

unread,
Dec 24, 2013, 9:20:36 PM12/24/13
to golan...@googlegroups.com, Kevin Gillette
I still vote for panicking on popping an empty stack, though.

Rasmus Schultz

unread,
Dec 25, 2013, 8:26:19 AM12/25/13
to Jsor, golan...@googlegroups.com, Kevin Gillette
You can make precisely the same arguments about null pointers, though

Except, no, you won't get very far if you try to treat a null-pointer as a Node instance - you will get an error and an immediate stack trace that tells you where you went wrong.

A function that can't really provide a pointer to a valid address, but returns a pointer anyway, is essentially just telling a lie

This function doesn't return a pointer to any valid address, it return a pointer to a Node - or nil if there is no Node to point at, which to me is simple, straight forward and honest.

You can apply the exact same line of argumentation to null objects in any other language, and it will still potentially cause all the same problems. And none of those problems are language dependent.

I understand your line of argumentation, I just don't agree with it.

egon

unread,
Dec 25, 2013, 1:09:20 PM12/25/13
to golan...@googlegroups.com, Jsor, Kevin Gillette


On Wednesday, December 25, 2013 3:26:19 PM UTC+2, Rasmus Schultz wrote:
You can make precisely the same arguments about null pointers, though

Except, no, you won't get very far if you try to treat a null-pointer as a Node instance - you will get an error and an immediate stack trace that tells you where you went wrong.


Not necessarily... calling a method on a nil value is perfectly valid. E.g. you can use it for easier tree traversing code... http://play.golang.org/p/tkzTZgOWEX

mikhae...@gmail.com

unread,
Dec 25, 2013, 2:53:43 PM12/25/13
to golan...@googlegroups.com, Rasmus Schultz

Don't worry so much about making this a var. This is fairly common for types that cannot be consts. For instance, it's used for errors all over:



What's preventing someone from writing ErrWriteToConnected = foo, and screwing everything up?

Chetan Gowda

unread,
Dec 25, 2013, 2:57:41 PM12/25/13
to golan...@googlegroups.com
I like the idea of keeping the data and errors separate. I feel the compiler should make it hard to ignore the error whenever a function returns one. I.e. mandate capturing errors returned by functions and warn when that error value is unused in the code immediately after the function call.

Rasmus Schultz

unread,
Dec 25, 2013, 3:36:14 PM12/25/13
to egon, golan...@googlegroups.com, Jsor, Kevin Gillette
That never even occurred to me, but since the language is statically-typed, yeah... huh :-)

But I don't think you're suggesting that every method should (or can) be written in such as way that it would work on a nil value?

egon

unread,
Dec 25, 2013, 4:19:30 PM12/25/13
to golan...@googlegroups.com, egon, Jsor, Kevin Gillette


On Wednesday, December 25, 2013 10:36:14 PM UTC+2, Rasmus Schultz wrote:
That never even occurred to me, but since the language is statically-typed, yeah... huh :-)

But I don't think you're suggesting that every method should (or can) be written in such as way that it would work on a nil value?


Obviously not... :)...

Luther

unread,
Dec 25, 2013, 6:46:52 PM12/25/13
to golan...@googlegroups.com, Rasmus Schultz, mikhae...@gmail.com

I don't see how you could make that mistake without actively trying to screw up. First of all, you'd have to qualify ErrWriteToConnected with the package name. Next, you'd have to make sure the value has an Error method. Also, when you refer to an error by its variable name, it's usually to test for equality, so the only thing that gets messed up is the error message output.

Rasmus Schultz

unread,
Dec 27, 2013, 9:08:03 AM12/27/13
to Luther, golan...@googlegroups.com, mikhae...@gmail.com
You could also just lowercase the variable name, that would keep it private to the package.

You could still modify it after triggering and receiving the error object, I suppose - but that's an even more far-fetched scenario ;-)

Note that I'm not looking for ways to make things "fool proof" or directly prevent a developer from doing something stupid (or evil) - what I look for, typically, is a design that communicates well what the intended use is. My attitude is: if you decide to break from obvious, good patterns that were put out there as the first option for you, that's your problem, not mine ;-)

Also, sometimes, code has legitimate uses that the author didn't foresee. Perhaps not often, but it happens, and I'm not a fan of building road blocks or providing training wheels for somebody who wants to take the road less traveled :-)

Rasmus Schultz

unread,
Dec 27, 2013, 9:51:20 AM12/27/13
to golan...@googlegroups.com
Okay, I struggled a bit with pointers and did some reading... apparently interfaces are always pointers to structs (?) which makes something like *interface{} completely redundant in my case.

So I worked on my queue and it's now storing pointers to Node instances, rather than copying them - I think... can you guys confirm?

package main

import "fmt"

// A generic Queue that can hold any interface type:

type Queue struct {
    head *Item
    tail *Item
}

type Item struct {
    value interface {}
    next *Item
}

func (q *Queue) Push(value interface {}) {
    item := &Item{value, nil}
    
    if q.tail != nil {
        q.tail.next = item
    }
    
    q.tail = item
    
    if q.head == nil {
        q.head = q.tail
    }
}

func (q *Queue) Pop() interface{} {
    if q.head == nil {
        panic("the queue is empty - use CanPop() before calling Pop()")
    }
    
    value := q.head.value
    
    q.head = q.head.next
    
    return value
}

func (q *Queue) CanPop() bool {
    return q.head != nil
}

// A custom element type and matching Queue type for it:

type Node struct {
    number int
}

type NodeQueue struct {
    Queue
}

func NewNodeQueue() NodeQueue {
    return NodeQueue{Queue{}}
}

func (q *NodeQueue) Push(n *Node) {
    q.Queue.Push(n)
}

func (q *NodeQueue) Pop() *Node {
    return q.Queue.Pop().(*Node)
}

func main() {
    q := NewNodeQueue()

    fmt.Printf("can pop? %t\n", q.CanPop())

    q.Push(&Node{0})
    fmt.Printf("can pop? %t\n", q.CanPop())
    fmt.Printf("pop: %d\n", q.Pop().number)
    
    q.Push(&Node{1})
    q.Push(&Node{2})
    q.Push(&Node{3})
    
    fmt.Printf("pop: %d\n", q.Pop().number)
    fmt.Printf("pop: %d\n", q.Pop().number)
    fmt.Printf("pop: %d\n", q.Pop().number)
    
    fmt.Printf("can pop? %t\n", q.CanPop())
}

Did I get it right? :-)

Jesse van den Kieboom

unread,
Dec 27, 2013, 10:40:29 AM12/27/13
to golan...@googlegroups.com
Don't forget to update q.tail in Pop when q.head becomes nil?

chris dollin

unread,
Dec 27, 2013, 10:40:59 AM12/27/13
to Rasmus Schultz, golang-nuts
On 27 December 2013 14:51, Rasmus Schultz <ras...@mindplay.dk> wrote:
Okay, I struggled a bit with pointers and did some reading... apparently interfaces are always pointers to structs (?)

No.

If the struct (or whatever) will fit in the pointer-sized space in the interface
value then that's what's in there.

which makes something like *interface{} completely redundant in my case.

Almost always a *interface{WOSSNAME} isn't right.

Chris
--
Chris "small bites" Dollin

Rasmus Schultz

unread,
Dec 27, 2013, 11:49:20 AM12/27/13
to chris dollin, golang-nuts
If the struct (or whatever) will fit in the pointer-sized space in the interface value then that's what's in there.

I see - but that's an under-the-hood optimization, right?

conceptually, I can think of interfaces as being references to structs?

and what does "WOSSNAME" mean?

chris dollin

unread,
Dec 27, 2013, 11:53:45 AM12/27/13
to Rasmus Schultz, golang-nuts
On 27 December 2013 16:49, Rasmus Schultz <ras...@mindplay.dk> wrote:
If the struct (or whatever) will fit in the pointer-sized space in the interface value then that's what's in there.

I see - but that's an under-the-hood optimization, right?

Yes, true. (An IMPORTANT one.)
 
conceptually, I can think of interfaces as being references to structs?

Conceptually: not structs -- any type. not references -- copies.

and what does "WOSSNAME" mean?

Thingy.
 
Chris

--
Chris "what's-his-name" Dollin

Rasmus Schultz

unread,
Dec 27, 2013, 11:55:12 AM12/27/13
to Jesse van den Kieboom, golan...@googlegroups.com
Yeah, you're right - like this?

func (q *Queue) Pop() interface{} {
    if q.head == nil {
        panic("the queue is empty - use CanPop() before calling Pop()")
    }
    
    value := q.head.value
    
    q.head = q.head.next
    
    if q.head == nil {
        q.tail = nil
    }
    
    return value
}

I would have caught that in a unit-test most likely, but I haven't written one yet :-)



Rasmus Schultz

unread,
Dec 27, 2013, 12:00:33 PM12/27/13
to chris dollin, golang-nuts
Conceptually: not structs -- any type. 

right! got it, thanks :-)

> not references -- copies.

copies? I don't understand - when would they get copied?

I need to tinker more with interfaces in the playground and get a handle on this...


chris dollin

unread,
Dec 27, 2013, 12:09:17 PM12/27/13
to Rasmus Schultz, golang-nuts
On 27 December 2013 17:00, Rasmus Schultz <ras...@mindplay.dk> wrote:
Conceptually: not structs -- any type. 

right! got it, thanks :-)

> not references -- copies.

copies? I don't understand - when would they get copied?

When you do an assignment / pass a parameter, "just like" the
usual way -- that is, if you assign a struct value to an interface
variable, then fiddle with the struct, that doesn't change what the
interface stores.
 
eg:

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

Chris

--
Chris "allusive" Dollin

Kevin Gillette

unread,
Dec 27, 2013, 2:58:12 PM12/27/13
to golan...@googlegroups.com, chris dollin
On Friday, December 27, 2013 10:00:33 AM UTC-7, Rasmus Schultz wrote:
> not references -- copies.

copies? I don't understand - when would they get copied?

Go is a strictly pass-by-value language, where 'pass' also includes assignments and expressions. Any operation that involves 'passing' a value in Go semantically passes a copy.  Go has "reference types", but those aren't "references" in the sense many programmers are used to, just as pointers don't fit that concept of a reference; in Go, everything is a value -- you just ought to know what nature of a given value is.

Rasmus Schultz

unread,
Dec 28, 2013, 9:00:11 AM12/28/13
to chris dollin, golang-nuts
So in your example,

var t T = s

defines a new variable of an interface type, and implicitly copies the contents of s.

so if what I wanted was a reference and not copy, I would do this:

var t T = &s

interesting... so structs really don't behave like objects in other languages - your default (or in some languages your only) option is not to pass them around by reference, but to treat them just the same as any other value.

I can think of a few cases where that would be a real advantage - for natural value-types such as date, time, date/time-range or color models, this is often a real pain in other languages where you have to design such types either to be immutable objects where every method that modifies the value has to return a new object.

But that also means you have to pay attention though, and take care to avoid e.g. copying expensive structs, right?

I can also think of examples where copying would actually cause problems - simplest example I can think of: if you accidentally made a copy of a service object that has an internal counter, that was supposed to return the next unique number in a sequence. If you have two copies, each copy will continue counting independently from when it was copied.

In cases like those, would you need to hide the object behind an API somehow, so it can't be copied? Or is there a better way to handle that?

Rasmus Schultz

unread,
Dec 28, 2013, 9:06:34 AM12/28/13
to Kevin Gillette, golan...@googlegroups.com, chris dollin
Go is a strictly pass-by-value language, where 'pass' also includes assignments and expressions

I head read that, but they didn't elaborate - the big difference for me is that structs are not "objects" in the typical sense, they are actually more like "set of values" if you will (?)

I also seem to recall reading somewhere that that's not entirely true in the case of slices though, is that right? Are slice elements really values?



--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/4yBldwsjZwo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

chris dollin

unread,
Dec 28, 2013, 10:48:35 AM12/28/13
to Rasmus Schultz, golang-nuts
--
Chris "allusive" Dollin

chris dollin

unread,
Dec 28, 2013, 10:58:24 AM12/28/13
to Rasmus Schultz, golang-nuts
On 28 December 2013 14:00, Rasmus Schultz <ras...@mindplay.dk> wrote:
So in your example,

var t T = s

defines a new variable of an interface type, and implicitly copies the contents of s.

Yes -- just as if T were the type of s, assigning to t copies s.

so if what I wanted was a reference and not copy, I would do this:

var t T = &s

Just so. Now t is a copy of the pointer-to-s that &s creates. Copying
a pointer is dirt-cheap.

 
interesting... so structs really don't behave like objects in other languages

Indeed -- they behave like structs (C structs or ISO Pascal RECORDs or
Algol 68 structs (which is probably where C got the term from since the
ancestor language BCPL didn't have structs)
 
- your default (or in some languages your only) option is not to pass them around by reference, but to treat them just the same as any other value.

Yes. They're proper values in their own right.
 
But that also means you have to pay attention though, and take care to avoid e.g. copying expensive structs, right?

Yes -- but "paying attention" is pervasive in programming. Remember that
dereferencing a pointer isn't cheap if the pointed-to value isn't cached.

I can also think of examples where copying would actually cause problems - simplest example I can think of: if you accidentally made a copy of a service object that has an internal counter, that was supposed to return the next unique number in a sequence. If you have two copies, each copy will continue counting independently from when it was copied.

Yes, and if that's importantly wrong, you (the programmer) has to guard
against it somehow.
 
In cases like those, would you need to hide the object behind an API somehow, so it can't be copied? Or is there a better way to handle that?

 As you say, you can hide it behind an API or and/or use a pointer so
that copying (the pointer) still leaves the counter shared.

chris dollin

unread,
Dec 28, 2013, 11:03:45 AM12/28/13
to Rasmus Schultz, Kevin Gillette, golang-nuts
On 28 December 2013 14:06, Rasmus Schultz <m...@rasmus-schultz.com> wrote:
Go is a strictly pass-by-value language, where 'pass' also includes assignments and expressions

I head read that, but they didn't elaborate - the big difference for me is that structs are not "objects" in the typical sense, they are actually more like "set of values" if you will (?)

A bunch of named values is what they are. You could think of them as
maps where all the lookup is done at compile-time and copying copies
all the fields.
 
I also seem to recall reading somewhere that that's not entirely true in the case of slices though, is that right? Are slice elements really values?

Slice-of-T elements are locations of type T, so you can copy T values
into them and get them back out and take their addresses.

The thing is that if you copy a slice value you it doesn't copy the
underlying array, just as if it were a struct with a pointer-to-something
field where copying the struct copies the pointer but not the pointed-to.

Chris

--
Chris "allusive" Dollin

Luther

unread,
Dec 28, 2013, 12:41:32 PM12/28/13
to golan...@googlegroups.com, Rasmus Schultz, ehog....@googlemail.com

Or you could use an unexported counter variable instead of a struct field.

Luther

Kevin Gillette

unread,
Dec 28, 2013, 4:30:40 PM12/28/13
to golan...@googlegroups.com, Kevin Gillette, chris dollin
On Saturday, December 28, 2013 7:00:11 AM UTC-7, Rasmus Schultz wrote:
var t T = s
 
defines a new variable of an interface type, and implicitly copies the contents of s.

There's nothing implicit about it -- it's explicitly defined as part of the language. Don't assume that it's expensive because it's a "copy" -- for the purpose of this discussion, value copies that are under a dozen or so machine-words in size can be thought of as being free.

Think of it like this: the above statement is storing a copy of s in t. s could be a pointer, or not -- the language/runtime doesn't care.
 
var t T = &s

This statement is storing a copy of a pointer to s in t. s itself could be a pointer (or contain pointers) or not -- the language/runtime doesn't care.

In other words, t gets assigned the result of evaluating the right hand side of the statement -- whether the address operator is involved or not is inconsequential to this fact.
 
 interesting... so structs really don't behave like objects in other languages - your default (or in some languages your only) option is not to pass them around by reference, but to treat them just the same as any other value.

Go has aspects of 'object oriented programming' (some would say in the very traditional sense -- behaviors and messages) -- but the only time anyone really discusses 'objects' in Go is when done from the perspective of how the GC manages values -- as mostly opaque entities of a known size that either are/aren't/contain/don't-contain pointers. So no, Go values don't behave like objects in the languages you're referring to (and *everything* that can be stored in a variable is a "value" in Go).
 
But that also means you have to pay attention though, and take care to avoid e.g. copying expensive structs, right?

Go saves you from segfaults and other "hard errors" because of its memory/type safety, but assumes you broadly know what you're doing when it comes to memory and performance. If you're copying a "semantically can be a non-pointer" 32kb struct value around in non-critical code, you probably won't notice any performance issues. In performance critical code, often a 6-8 word-sized struct can be faster to access than a pointer to that struct would be to dereference; of course. Main lesson: worry about semantics first (if you need a pointer, use a pointer), then if you notice any unexpected performance characteristics, profile -- only then should you worry much about optimization.
 
if you accidentally made a copy of a service object that has an internal counter, that was supposed to return the next unique number in a sequence

That's what pointers are (semantically) intended to be used for.

Go is a strictly pass-by-value language, where 'pass' also includes assignments and expressions

I head read that, but they didn't elaborate - the big difference for me is that structs are not "objects" in the typical sense, they are actually more like "set of values" if you will (?)

I also seem to recall reading somewhere that that's not entirely true in the case of slices though, is that right?

I don't think structs need any real elaboration. They are exactly what you specify, with extra padding added for alignment as needed: for example, 8-byte values need to be 8-byte-aligned on typical 64-bit platforms, thus given `struct { x byte; y int64 }`, the whole struct will use 16 bytes, and x will be followed by 7 bytes of padding in order to align y.

Besides all that, I was completely serious in making the above "pass-by-value" generalization: it applies to the whole language, without exception, and all types, without exception, behave consistently and uniformly as "values" -- you're just expected to know what those values are. Slices, maps, and other "reference types" are not special-cased at all in this respect; the "value" being exposed is simply a "slice header" which internally is a struct containing a pointer and a length and cap pair of fields -- when you do a slice operation, such as s[:3], evaluating that expression produces a modified copy of the original slice header, but doesn't access any of the data pointed to by the data pointer.

See http://blog.golang.org/go-slices-usage-and-internals for a more in-depth explanation.

Are slice elements really values?

Everything's a value.

Rasmus Schultz

unread,
Dec 28, 2013, 5:51:31 PM12/28/13
to Kevin Gillette, golan...@googlegroups.com, chris dollin
Thanks, Kevin and Chris - you guys are super helpful!

Main lesson: worry about semantics first (if you need a pointer, use a pointer), then if you notice any unexpected performance characteristics, profile -- only then should you worry much about optimization.

I've been waiting to hear that from a Go developer, thanks - been getting a lot of performance-related advice up until now, and it's good to know some of you use Go not just because of it's high performance, but because the language semantics actually work for you :-)



--

Dan Kortschak

unread,
Dec 28, 2013, 7:17:58 PM12/28/13
to Rasmus Schultz, Kevin Gillette, golan...@googlegroups.com, chris dollin
That's probably most of us.

Kevin Gillette

unread,
Dec 28, 2013, 10:57:00 PM12/28/13
to golan...@googlegroups.com, Rasmus Schultz, Kevin Gillette, chris dollin
Agreed. I'd continue to use Go as my primary language even if it were slower than Ruby (or <insert notoriously slow language here>).

Kevin Gillette

unread,
Dec 29, 2013, 3:21:24 PM12/29/13
to golan...@googlegroups.com, Rasmus Schultz, Kevin Gillette, chris dollin
To clarify, when I've said 'copy' in this thread, I mean the operation of copying the data from a known source into a known destination, such as is done with the copy builtin. I did not mean "allocate space for a copy on the heap," which does not implicitly occur through assignment or argument passing (though it can occur for at function entry for escaping variables, since those cannot be stack-allocated).

Rasmus Schultz

unread,
Dec 30, 2013, 8:13:49 AM12/30/13
to Dan Kortschak, Rasmus Schultz, Kevin Gillette, golan...@googlegroups.com, chris dollin
I got a lot of performance-oriented advice early on, when I started asking questions on the forums - a lot of "this could be faster" or "don't do that, you can save a few bytes of memory", which gave me the impression that a lot of Go developers use it for it's speed, or care deeply about writing highly optimized code.

I never picked any language for performance, except possibly for assembler in my early teens on 8-bit machines ;-)

Reply all
Reply to author
Forward
0 new messages