What's idiomatic way to initialize struct with map?

10,192 views
Skip to first unread message

Boris Solovyov

unread,
Dec 22, 2012, 1:15:44 PM12/22/12
to golang-nuts
Hi awesome list members,

Following code will break because map is nil. (http://play.golang.org/p/qqZMrG95x7):

package main

import "fmt"

type EuclideanPoint struct {
X, Y int
}

type PointsOfInterest struct {
points map[string]EuclideanPoint
}

func (p *PointsOfInterest) Add(name string, e EuclideanPoint) {
p.points[name] = e
}

func main() {
p := PointsOfInterest{}
p.Add("home", EuclideanPoint{100, 100})
p.Add("office", EuclideanPoint{200, 200})
fmt.Println(p)
}

In my code so far, I solve this in two ways:

1. Have func NewPointsOfInterest() that returns a struct with initialized map.
2. Have Add() func check for nil and initialize map if needed.

I don't like 1. because now everywhere has to remember to call "constructor" and I don't like 2. because now all other methods of struct has to perform the same check.

Is there a way to have PointsOfInterest's "zero value" be ready to use? Or another way besides 1. and 2. I listed?

Thanks!
Boris

Kyle Lemons

unread,
Dec 22, 2012, 1:32:30 PM12/22/12
to Boris Solovyov, golang-nuts
1 and 2 are both legal.  You could also just allow POI to be intialized completely without requiring Add methods:

Boris Solovyov

unread,
Dec 22, 2012, 3:41:07 PM12/22/12
to Kyle Lemons, golang-nuts
On Sat, Dec 22, 2012 at 1:32 PM, Kyle Lemons <kev...@google.com> wrote:
On Sat, Dec 22, 2012 at 1:15 PM, Boris Solovyov <boris.s...@gmail.com> wrote:
Hi awesome list members,

Following code will break because map is nil. (http://play.golang.org/p/qqZMrG95x7):
[snip]

In my code so far, I solve this in two ways:

1. Have func NewPointsOfInterest() that returns a struct with initialized map.
2. Have Add() func check for nil and initialize map if needed.

I don't like 1. because now everywhere has to remember to call "constructor" and I don't like 2. because now all other methods of struct has to perform the same check.

Is there a way to have PointsOfInterest's "zero value" be ready to use? Or another way besides 1. and 2. I listed?
1 and 2 are both legal.  You could also just allow POI to be intialized completely without requiring Add methods:

Sure. But your suggestion makes code more dependent on the internals of the struct that I want to abstract, so that I have to change code all over my repository every time I change the struct definition :-{

Seems this is one of tradeoffs of Go... lack of magical constructor that is auto-executed. Well, no big deal, just wondering if there was a better way I did not see.

Patrick Mylund Nielsen

unread,
Dec 22, 2012, 3:51:42 PM12/22/12
to Boris Solovyov, Kyle Lemons, golang-nuts
A New() function is the idiomatic way if the struct needs any kind of setup beyond simple stuff. It is convention, but in my experience it's not confusing. Most structs in most libraries need some kind of setup, and thus have such a constructor function.

It might have been convenient if types had special init() methods, and new() caused them to be invoked, but it would cause some confusion with composite literals/cases where you actually want the fields to be uninitialized.


--
 
 

Kevin Gillette

unread,
Dec 22, 2012, 5:28:08 PM12/22/12
to golan...@googlegroups.com, Boris Solovyov, Kyle Lemons
Agreed. In Go you should not go out of your way to protect users of your library code who can't be bothered to notice the existence of a New function. For example, if you run <http://play.golang.org/p/YxzyzfxNYZ> outside of the playground, you'll get a nil deference panic -- certainly the os package could have been designed to return an unexported type in order to protect against a naive user from using an uninitialized value, but that would inconvenience typical programmers and the style of adding runtime checks in each method is usually inefficient both for the library author and in the code. There are some exceptions to this (sync.Mutex, bytes.Buffer) where the zero value is useful, and these cases are documented as such.

In general, expect users of your libraries to be competent, and you'll save yourself and your average users time by keeping your API simple. If you're worried about naive programmers: it doesn't help them if your library is the only one in the Go community that goes out of its way to protect them against their own mistakes: they will make many mistakes no matter what you do, and their only solution is to identify and correct their own shortcomings, or earnestly seek advice about how to become a better Go programmer.

Sean Russell

unread,
Dec 24, 2012, 12:02:44 PM12/24/12
to golan...@googlegroups.com, Boris Solovyov, Kyle Lemons
On Saturday, December 22, 2012 12:51:42 PM UTC-8, Patrick Mylund Nielsen wrote:
It might have been convenient if types had special init() methods, and new() caused them to be invoked, but it would cause some confusion with composite literals/cases where you actually want the fields to be uninitialized.

So provide an init() function following the module init pattern, and have it called by make() (which would require normalizing make() so that it works with more than just arrays, maps, and chans). Solve two Go ... idiosyncrasies ... at once, without changing the behavior of any existing programs.

--- SER

Kyle Lemons

unread,
Dec 24, 2012, 2:21:45 PM12/24/12
to Sean Russell, golang-nuts, Boris Solovyov
Except that has the same problem as pretty much every other bit of magic: make() and new() have very well-understood behavior, just as + and / do.  If you suddenly start allowing certain types to implement arbitrary behavior (read: potentially unlimited side effects) you make code harder to read, understand, test, and debug.

Patrick Mylund Nielsen

unread,
Dec 24, 2012, 2:34:27 PM12/24/12
to Kyle Lemons, Sean Russell, golang-nuts, Boris Solovyov
You could say the same about function calls, then all of a sudden you're calling for referential transparency.

When we call a package's New() function, we expect to get a new Foo, not for the package to launch the missiles. It wouldn't be unreasonable to think the same of new(), just as we do of a package's init() when we start out program. That said, I do not think that an implicit init() for a type is a good idea, for other reasons. Sometimes you really want just &T{}.


--
 
 

Kevin Gillette

unread,
Dec 24, 2012, 8:07:34 PM12/24/12
to golan...@googlegroups.com, Boris Solovyov, Kyle Lemons
I don't think "lack of magic" in a language is idiosyncratic. The language does not have any special methods for special behavior. Adding special-cased support for an init() method _would_ create an idiosyncrasy.

Sean Russell

unread,
Dec 24, 2012, 8:29:06 PM12/24/12
to golan...@googlegroups.com, Boris Solovyov, Kyle Lemons
On Monday, December 24, 2012 5:07:34 PM UTC-8, Kevin Gillette wrote:
I don't think "lack of magic" in a language is idiosyncratic. The language does not have any special methods for special behavior. Adding special-cased support for an init() method _would_ create an idiosyncrasy.

You already have the magic.  Modules in Go already have an init() function.  It's opaque, black box magic that "launches missiles" when you include and use a package, if it is defined.

Go has a lot of things that are idiosyncratic in that they're not intuitive (extrapolate-able based on partial knowledge of the language rules) across the language: this is one of them. In many cases, there are good, well thought-out reasons for the inconsistency.  Maybe there's a good reason in this case, too.  "Protecting developers from themselves because developers are stupid" is not one of them.

--- SER

Mike Rosset

unread,
Dec 25, 2012, 12:25:23 AM12/25/12
to Sean Russell, golang-nuts, Boris Solovyov, Kyle Lemons
I'm not sure I would go so far as to say func init is magic . Its
really only there to setup internal package state. Done right it
should not really bite anyone in the ass. GOROOT/src/pkg/fmt/format.go
is a good example of using init to initialize some internal maps, all
of which is hidden from the user of the package.
> --
>
>

Kyle Lemons

unread,
Dec 25, 2012, 2:18:04 AM12/25/12
to Patrick Mylund Nielsen, Sean Russell, golang-nuts, Boris Solovyov
On Mon, Dec 24, 2012 at 2:34 PM, Patrick Mylund Nielsen <pat...@patrickmylund.com> wrote:
You could say the same about function calls, then all of a sudden you're calling for referential transparency.

When debugging code, if you've already determined that function X is returning the wrong value but the logic itself looks right, you want to be able to look at the function and understand where things could be going wrong.  In Python or C++, every method call, operator, assignment, construction, value leaving scope, return, index, increment, dereference, or comparison is a potential source of unintended side-effects (along with many others).  In Go, on the other hand, what you see is what you get.  An assignment is just an assignment; an addition is just an addition.  Allocating a new object or creating a new map is just that.  Calling a function is, and will always be, a potential error source but in Go they are easy to spot.

Yes, init() is magical, and I would prefer it if importing a package couldn't change the behavior of your code, but I think this particular case is a necessity.

Dan Kortschak

unread,
Dec 25, 2012, 4:56:46 AM12/25/12
to Kyle Lemons, Patrick Mylund Nielsen, Sean Russell, golang-nuts, Boris Solovyov
I was thinking about this the other day. Is init really necessary? You can get an awful lot of init functionality from assigning the return value of a function to a package level variable, e.g.

_ = func() int {
// various side effect causing actions
return 0
}()

This doesn't guarantee all variables are initialised though (I think), so that's a difference. I guess the main one. What is the order of package-level variable initialisation? It does not appear to be specified, e.g. http://play.golang.org/p/08Fa0PgVcC

This is more ugly than an init func, but it shows that init is not really magical (except for the timing guarantee provided by an init).

Dan

chris dollin

unread,
Dec 25, 2012, 5:07:40 AM12/25/12
to Dan Kortschak, Kyle Lemons, Patrick Mylund Nielsen, Sean Russell, golang-nuts, Boris Solovyov
On 25 December 2012 09:56, Dan Kortschak wrote:

>What is the order of package-level variable initialisation? It does not appear to be specified, e.g. http://play.golang.org/p/08Fa0PgVcC

http://golang.org/ref/spec#Program_execution

...

Within a package, package-level variables are initialized, and
constant values are determined, in data-dependent order: if the
initializer of A depends on the value of B, A will be set after B. It
is an error if such dependencies form a cycle. Dependency analysis is
done lexically: A depends on B if the value of A contains a mention of
B, contains a value whose initializer mentions B, or mentions a
function that mentions B, recursively. If two items are not
interdependent, they will be initialized in the order they appear in
the source. Since the dependency analysis is done per package, it can
produce unspecified results if A's initializer calls a function
defined in another package that refers to B.

(etc)

Chris

--
Chris "allusive" Dollin

Dan Kortschak

unread,
Dec 25, 2012, 5:29:54 AM12/25/12
to chris dollin, Kyle Lemons, Patrick Mylund Nielsen, Sean Russell, golang-nuts, Boris Solovyov
Thanks, I actually read that and yet somehow completely managed to miss everything. Too much Christmas.

So you can potentially mimic an init function perfectly with that technique.

Kevin Gillette

unread,
Dec 25, 2012, 2:07:01 PM12/25/12
to golan...@googlegroups.com, Dan Kortschak, Kyle Lemons, Patrick Mylund Nielsen, Sean Russell, Boris Solovyov, ehog....@googlemail.com
Why would constants need to be determined at runtime? The whole point is that they're constants, which cannot be composed of non-constant expressions, and thus can packed into the text segment during compilation, or set to be all bulk loaded into memory before any package initialization occurs.
Reply all
Reply to author
Forward
0 new messages