Sunday, June 5, 2011

Anonymous fields in structs - like object composition


Go allows you to define a struct that has fields but with no variable names. These fields are called anonymous fields. Let’s do a few examples to find out what they are and how they will be useful.

In the below example, we have defined a Kitchen struct, which only contains the number of plates as a field. We define another field called House, which contains an instance of Kitchen as a field - but it is an anonymous field, because we have not given a variable name for it.

Full code
package main

import "fmt"

type Kitchen struct {
    numOfPlates int 
}

type House struct {
    Kitchen //anonymous field
    numOfRooms int 
}

func main() {
    h := House{Kitchen{10}, 3} //to initialize you have to use composed type name.
    fmt.Println("House h has this many rooms:", h.numOfRooms) //numOfRooms is a field of House
    fmt.Println("House h has this many plates:", h.numOfPlates) //numOfPlates is a field of anonymous field Kitchen, so it can be referred to like a field of House
    fmt.Println("The Kitchen contents of this house are:", h.Kitchen) //we can refer to the embedded struct in its entirety by referring to the name of the struct type
}

House h has this many rooms: 3
House h has this many plates: 10
The Kitchen contents of this house are: {10}

The first important thing to note is that since we have defined Kitchen to be an anonymous field, it allows us to access its members as if they were members of the encompassing class. In comparison, if you were using a language like Java, you would have to do:

Partial Java code
public class Kitchen {
    public int numOfPlates;
}

public class House {
    public Kitchen kitchen;
}

//and in main
public static void main(String[] args) {
    House h = new House();
    h.kitchen.numOfPlates = 10; //referred as a sub field item.
}

The second important thing to note is that the composed field is still available to be accessed, but by its type name. So in this case the anonymous field for Kitchen has to be accessed as h.Kitchen. If you want to print the number of plates in the kitchen, then do this: fmt.Println(h.Kitchen.numOfPlates).

And the third important thing to note is how we had to use the type name when initializing the values as we did: h := House{Kitchen{10}, 3}. It is necessary here that you state the type name and provide its values within the corresponding curly braces. So h := House{{10}, 3} and h := House{10, 3} will cause compilation errors.

Anonymous fields - when naming conflicts arise

What happens when more than one of the composed structs or the composing struct has the same field name. If there is a field in an outer struct with the same name as a field in an inner anonymous struct, then the outer one is accessible by default. In the below example, both the Kitchen and the House has a numOfLamps field, but since House is the outer struct, its numOfLamps hides Kitchen’s. If you still require to access the Kitchen’s numOfLamps, that is possible by referring to it via the type name: h.Kitchen.numOfLamps.

Full code
package main

import "fmt"

type Kitchen struct {
    numOfLamps int
}

type House struct {
    Kitchen
    numOfLamps int
}

func main() {
    h := House{Kitchen{2}, 10} //kitchen has 2 lamps, and the House has a total of 10 lamps
    fmt.Println("House h has this many lamps:", h.numOfLamps) //this is ok - the outer House's numOfLamps hides the other one.  Output is 10.
    fmt.Println("The Kitchen in house h has this many lamps:", h.Kitchen.numOfLamps) //we can still reach the number of lamps in the kitchen by using the type name h.Kitchen
}

House h has this many lamps: 10
The Kitchen in house h has this many lamps: 2

So there is a rule on field resolution when the same field occurs at different levels of composition. But there is no rule when the fields are at the same level of composition - which means that when it occurs you need to resolve it yourself.

In the code below, both the Kitchen and the Bedroom have a field numOfLamps, and both are available as an anonymous field within House. Now if we referred to House.numOfLamps, the Go compiler cannot resolve whether you are referring to the numOfLamps within Kitchen or that within Bedroom and it throws an error.

Full file: structs2.go
package main

import "fmt"

type Kitchen struct {
    numOfLamps int
}

type Bedroom struct {
    numOfLamps int
}

type House struct {
    Kitchen
    Bedroom
}

func main() {
    h := House{Kitchen{2}, Bedroom{3}} //kitchen has 2 lamps, Bedroom has 3 lamps
    fmt.Println("Ambiguous number of lamps:", h.numOfLamps) //this is an error due to ambiguousness - is it Kitchen.numOfLamps or Bedroom.numOfLamps
}

Compiler error
8g -o _go_.8 structs2.go
structs2.go:20: ambiguous DOT reference House.numOfLamps
make: *** [_go_.8] Error 1

To resolve this, you will have to refer to the required fields explicitly via the type name of the anonymous field. In the corrected example below, we’ve summed up the number of lamps in the kitchen and the bedroom by referring to the number of lamps in it via its type name.

package main

import "fmt"

type Kitchen struct {
    numOfLamps int
}

type Bedroom struct {
    numOfLamps int
}

type House struct {
    Kitchen
    Bedroom
}

func main() {
    h := House{Kitchen{2}, Bedroom{3}}
    fmt.Println("House h has this many lamps:", h.Kitchen.numOfLamps + h.Bedroom.numOfLamps) //refer to fields via type name
}

House h has this many lamps: 5


8 comments:

  1. Articles are very good, although it's more like a book)
    Thank you.

    ReplyDelete
  2. 'h := House{Kitchen{2}, Bedroom{3}} //kitchen has 2 lamps, Bedroom has 3 lamps, and the House has a total of 10 lamps'

    There's no integer being set to 10 there or anywhere in that example.

    ReplyDelete
  3. Thank you, have updated the comment in the code.

    ReplyDelete
  4. But what if we need to define two or more Kitchen for House?

    ReplyDelete
  5. in the java code....
    public class Kitchen {
    int numOfPlates;
    }

    it's not public... it's default( ) accessor: go lower case field/struct/funcion can be seen within package level.

    ReplyDelete
    Replies
    1. In the Java code, I've written "public int numOfPlates". I think you dropped the 'public' keyword in your comment.

      Delete
  6. How would you define a struct with multiple anonymous fields of the same type i.e. array of anonymous fields. E.g How to define a House with multiple Bedrooms (in reference to your last example).

    ReplyDelete
    Replies
    1. type Bedroom struct {
      numOfRooms int
      }

      type Bedrooms []Bedroom

      type House struct {
      Bedrooms
      }

      func main() {
      bedrooms := Bedrooms{Bedroom{1}, Bedroom{2}, Bedroom{3}}
      house := House{bedrooms}
      fmt.Println(len(house.Bedrooms))
      }

      Delete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.