Default arguments in struct

5,500 views
Skip to first unread message

chl

unread,
Jul 23, 2012, 8:14:08 PM7/23/12
to golan...@googlegroups.com
Currently all fields in a struct are initialized to their zero value. The result of this is that we currently need a separate constructor for structs that require types such as "map" and "chan".

This problem could be solved by enabling the use of default values/value assignment in structs. For example:

type Foo struct {
     a int = 1
     m map[string]int = make(map[string]int)
}

or perhaps

type Foo struct {
     a := 1
     m := make(map[string]int)
}

So my question is why is this not allowed in Go? what are the downside to this solution?

Kyle Lemons

unread,
Jul 23, 2012, 8:25:10 PM7/23/12
to chl, golan...@googlegroups.com
You are generally encouraged to arrange for your structs to be properly initialized in their zero state or to provide a suitable New function for them.

On Mon, Jul 23, 2012 at 5:14 PM, chl <chiah...@gmail.com> wrote:
Currently all fields in a struct are initialized to their zero value. The result of this is that we currently need a separate constructor for structs that require types such as "map" and "chan".

This problem could be solved by enabling the use of default values/value assignment in structs. For example:

type Foo struct {
     a int = 1
     m map[string]int = make(map[string]int)
}

This is problematic because it is not clear whether that is one make() per instantiation or one make per execution of the program.  Both behaviors make sense in varying circumstances.  It's these sorts of nuances that make the simple rule that uninitialized values (in structs and otherwise) are zero, and the zero values are clearly defined.

Michael Jones

unread,
Jul 23, 2012, 8:36:53 PM7/23/12
to Kyle Lemons, chl, golan...@googlegroups.com
Hmmm...

Well, as it is we need a rule to decide if the allocated memory is set to zero once per struct instantiation or once per execution of the program. Both behaviors make sense in varying circumstances. The Go authors did not seem to have a problem making a rule about that. If they wanted to have "assignments in a struct definition as a template for initialization" then the same rule as for zeroes would be natural, consistent, and clearly defined.

Not saying that they do, but this aspect seems unlikely to be the sticking point. IIRC, the rationale was something like "arbitrary constructors and destructors are complicated when concurrency is implicit." Maybe this (or some other) is a better reason for discouraging the idea. It certainly has a natural appeal. It is simple to say and definitive. I have a struct with a boolean, and I perverted the whole program logic so that it could be false by default; being able to write "struct... valid bool = true" would have been sweet!
--
Michael T. Jones | Chief Technology Advocate  | m...@google.com |  +1 650-335-5765

Ingo Oeser

unread,
Jul 24, 2012, 4:45:50 AM7/24/12
to golan...@googlegroups.com
You can always build a "func NewFoo() *Foo {return &Foo{valid : true}}" if you need it and your program looks crap with inverted logic.

But you rarely need it.

Ingo Oeser

unread,
Jul 24, 2012, 4:45:50 AM7/24/12
to golan...@googlegroups.com

chl

unread,
Jul 24, 2012, 5:54:52 AM7/24/12
to golan...@googlegroups.com
From my perspective in the two examples,

type Foo struct {
     a int = 1
     m map[string]int = make(map[string]int)
}

or perhaps

type Foo struct {
     a := 1
     m := make(map[string]int)
}

the make is quite clear (meaning that a new map is created with each initiation of Foo). Its kinda like calling a function that makes something, each time you call it, you are creating a new map. If you want all the Foo to share the same map than we can write it like this:

var m = make(map[int]string)

type Foo struct {
     a int = 1
     m map[int]string = m
}

Michael Jones

unread,
Jul 25, 2012, 1:12:54 AM7/25/12
to Ingo Oeser, golan...@googlegroups.com
Excellent point! THanks


On Tue, Jul 24, 2012 at 1:45 AM, Ingo Oeser <night...@googlemail.com> wrote:
You can always build a "func NewFoo() *Foo {return &Foo{valid : true}}" if you need it and your program looks crap with inverted logic.

But you rarely need it.



roger peppe

unread,
Jul 25, 2012, 3:49:06 AM7/25/12
to chl, golan...@googlegroups.com
Another possibility not mentioned here is that you
can initialise lazily:

type Foo struct {
m map[string]int
}

func (f *Foo) init() {
if f.m == nil {
f.m = make(map[string]int)
}
}

func (f *Foo) DoSomething() {
f.init()
doSomething(f)
}

This can also be thread-safe if desired, because
the sync data structures are ok to use in their
zero state.

type Foo struct {
initializeOnce sync.Once
m map[string]int
}
func (f *Foo) init() {
f.initializeOnce.Do(func(){
f.m = make(map[string]int)
})
}

or with a mutex:

type Foo struct {
mu sync.Mutex
m map[string]int
}
func (f *Foo)init() {
if f.m == nil {
f.m = make(map[string]int)
}
}
func (f *Foo) DoSomething() {
f.mu.Lock()
defer f.mu.Unlock()
f.init()
}

Having universal zero initialisation and no automatic constructors
is great. It means that doing:

var f [200]Foo

does not need to invoke a constructor function
200 times behind the scenes, and that creating
types with other value-embedded types is as simple
as allocating one zeroed block with all the types.

chl

unread,
Jul 25, 2012, 10:24:12 PM7/25/12
to golan...@googlegroups.com
Thats an interesting way to do it rog. Although there are small penalties in performance and code complexity it does achieve the same effect as using a default argument in a struct without changing the language in anyway. As usual thank you rog for adding another pattern to my ever growing arsenal.

Putting that aside, I don't agree that the performance penalty for doing:

var f [200]Foo

is that much different between the current method and calling a constructor 200 times. The reason being that, if you are creating [200]Foo, it can be assumed that you'll have to go through each element and manually create and assign a Map to each of the Foo. Even if we use lazy initialization, the checks used in every method, can arguably negate any performance gains from not calling a constructor. So essentially the performance should be the same.

In addition, with the current mechanics, the amount of code you'll need to write increases quickly when you embed more slices that require initialization into a struct (non-slice/array fields don't have much of an effect though). For example:

type Foo struct {
     data interface{}
     m map[string]int
     f [200]Buu
}

type Buu struct {
     data interface{}
     m map[string]int
}

to properly initialize:
var f [200]Foo

you'll first need an outer loop that creates a map for each of the Foo's and assign them to the Foo.m. Than you'll need another inner loop to loop through Foo.f. To create a map for each Buu. 

Compare this to using default arguments in struct. Where we could do

type Foo struct {
     data interface{}
     m := make(map[string]int)
     f [200]Buu
}

type Buu struct {
     data interface{}
     m := make(map[string]int)
}

we would not need any loops for initialization, which I argue makes things much simpler and less verbose. And be reminded that this is only a single slice embedded in a struct.


Having said that though, I do believe that the current implementation is much simpler implementation wise and that allows for maintainability, speedy execution, as well as maintains language simplicity. These are all great things.

Lastly, just out of curiosity rog, if you had to implement something like a default argument for structs how would you do it syntax wise, and implementation wise? (This is just for fun and the sake of discussion).

roger peppe

unread,
Jul 26, 2012, 4:21:46 AM7/26/12
to chl, golan...@googlegroups.com
On 26 July 2012 03:24, chl <chiah...@gmail.com> wrote:
> Lastly, just out of curiosity rog, if you had to implement something like a
> default argument for structs how would you do it syntax wise, and
> implementation wise? (This is just for fun and the sake of discussion).

I wouldn't. Types are usually specified in global scope, and at that
scope you often don't have access to the default values you'd like,
which often come from the context in which the object is being created.
Having default values for structs privileges global variables too much.

Actually, I tell a lie - sometimes I want default values for a struct, so
I'll declare a global varable of that struct type containing the defaults.
Obviously this doesn't allow new allocations when an object is created,
but that's often fine. Otherwise a constructor function seems just
fine to me - it's not much hassle to write, you've got the full generality
of the language available when constructing the defaults, and the
call is explicit in the code, so you know when there might be tricky
things going on.
Message has been deleted

Jonathan Amsterdam

unread,
Jul 27, 2012, 9:19:39 AM7/27/12
to golan...@googlegroups.com, chl
If struct fields can have initialization expressions, then those expressions can panic. That means variable declarations can panic. One advantage of Go is that things are what they seem -- in this case, that variable declarations run no code. 

There is also the embarrassing question of when these expressions are evaluated. E.g.

  var a int

  type Foo struct { i int = a }

  func init() {
    a = 1
    go assignTo_a_EveryOnceInAWhile()
  }

When I later declare an instance of Foo, which value of a will I get? Is my program even thread-safe?

Constructor functions aren't a perfect replacement. They force you to pick heap or stack allocation. E.g. func New() *Foo means I can't stack-allocate Foo's safely. I'd also need func (f *Foo) Init() so I could write

    var f Foo
    f.Init()

A possible middle ground would allow fields to be initialized with constant expressions. These can't panic (or if they can, the compiler can detect it). They are constant, so the question of evaluation time doesn't arise. They solve the common cases of valid = true and sliceIndex = -1. But they do destroy the nice implementation property that memory can simply be zeroed.

Reply all
Reply to author
Forward
0 new messages