Structs in Golang

Rajesh GurajalaRajesh Gurajala
6 min read

Topics to be covered in this article:

Structs

Nested structs

Anonymous structs

Nested anonymous structs

Embedded structs

Struct methods

We use structs in Go to group different types of variables together.

Structs in Go are often used to represent data that you might use a dictionary in PYTHON or object in JS

For example :

type car struct {
    brand      string
    model      string
    doors      int
    mileage    int
}

Usage :

package main
import "fmt"

type car struct {
    model int
    specs string
}

func info( c car) {
    fmt.Printf("the car model %v has specs %s \n", c.model, c.specs)
}

func main() {

    ford :=    car{model: 678, specs: "V8 engine"}
    info(ford)

}

Nested Structs

Structs can be nested to represent more complex entities:

type car struct {
  brand string
  model string
  doors int
  mileage int
  frontWheel wheel
  backWheel wheel
}

type wheel struct {
  radius int
  material string
}

The fields of a struct can be accessed using the dot . operator.

myCar := car{}
myCar.frontWheel.radius = 5

Usage :

package main
import "fmt"

type user struct {
    name string
    number int
}

type message struct {
    message string
    sender user
    recipient user
}

func canSendMessage(m message) bool {

    return m.sender.name != "" &&
    m.sender.number != 0 &&
    m.recipient.name != "" &&
    m.recipient.number != 0

}

/*
The function canSendMesage should return true 
only if the sender and recipient fields each contain a name and a number. 
If any of the default zero values are present, return false instead.
*/

Anonymous Structs in Go

An anonymous struct is just like a normal struct, but it is defined without a name and therefore cannot be referenced elsewhere in the code.

To create an anonymous struct, just instantiate the instance immediately using a second pair of brackets after declaring the type:

myCar := struct {
  brand string
  model string
} {
  brand: "Toyota",
  model: "Camry",
}

Nesting with anonymous struct :

type car struct {
  brand string
  model string
  doors int
  mileage int
  // wheel is a field containing an anonymous struct
  wheel struct {
    radius int
    material string
  }
}

var myCar = car{
  brand:   "Rezvani",
  model:   "Vengeance",
  doors:   4,
  mileage: 35000,
  wheel: struct {
    radius   int
    material string
  }{
    radius:   35,
    material: "alloy",
  },
}

In general, prefer named structs. Named structs make it easier to read and understand your code, and they have the nice side-effect of being reusable. I sometimes use anonymous structs when I know I won't ever need to use a struct again. For example, sometimes I'll use one to create the shape of some JSON data in HTTP handlers.

If a struct is only meant to be used once, then it makes sense to declare it in such a way that developers down the road won’t be tempted to accidentally use it again.

Embedded Structs

Go is not an object-oriented language. However, embedded structs provide a kind of data-only inheritance that can be useful at times. Keep in mind, Go doesn't support classes or inheritance in the complete sense, but embedded structs are a way to elevate and share fields between struct definitions.

type car struct {
  brand string
  model string
}

type truck struct {
  // "car" is embedded, so the definition of a
  // "truck" now also additionally contains all
  // of the fields of the car struct
  car
  bedSize int
}

Embedded vs Nested :

  • Unlike nested structs, an embedded struct's fields are accessed at the top level like normal fields.

  • Like nested structs, you assign the promoted fields with the embedded struct in a composite literal.

lanesTruck := truck{
  bedSize: 10,
  car: car{
    brand: "Toyota",
    model: "Tundra",
  },
}

fmt.Println(lanesTruck.brand) // Toyota
fmt.Println(lanesTruck.model) // Tundra

In the example above you can see that both brand and model are accessible from the top-level, while the nested equivalent to this object would require you to access these fields via a nested car struct: lanesTruck.car.brand or lanesTruck.car.model.

Embedded structs are useful in backend code where we have pydantic models in python fastapi ,

similarly to store data during signup we create struct having user info and password too,

but after signup we just return success status code with same user info without password.

Struct Methods in Go

While Go is not object-oriented, it does support methods that can be defined on structs. Methods are just functions that have a receiver. A receiver is a special parameter that syntactically goes before the name of the function.

type rect struct {
  width int
  height int
}

// area has a receiver of (r rect)
// rect is the struct
// r is the placeholder
func (r rect) area() int {
  return r.width * r.height
}

var r = rect{
  width: 5,
  height: 10,
}

fmt.Println(r.area())
// prints 50

A receiver is just a special kind of function parameter. In the example above, the r in (r rect) could just as easily have been rec or even x, y or z. By convention, Go code will often use the first letter of the struct's name.

Receivers are important because they will, as you'll learn in the exercises to come, allow us to define interfaces that our structs (and other types) can implement.

Another example:

package main
import "fmt"
type authenticationInfo struct {
    username string
    password string
}

func (a authenticationInfo) formatter() string{
    return fmt.Sprintf("Authorization: Basic %v:%v",a.username,a.password)
}

func main() {
    a := authenticationInfo{
        username: "raj",
        password: "1234",
    }

    fmt.Println(a.formatter())
}

Extra example codes on golang struct:

Example 1 :

package main
import "fmt"
type User struct {
    Name string
    Membership
}

type Membership struct{
    Type string
    limit int
}

func newUser(name string, mtype string) User {
    switch mtype {
    case "premium":
        return User{
            Name: name,
            Membership: Membership{
                Type: "premium",
                limit: 1000,
            },
        }
    default:
        return User{
            Name: name,
            Membership: Membership{
                Type: "basic",
                limit: 100,
            },
        }
    }
}

func main(){
fmt.Println("nice code")
}

Example 2 :

package main

type User struct {
    Name string
    Membership
}

type Membership struct {
    Type             string
    MessageCharLimit int
}

func newUser(name string, membershipType string) User {
    membership := Membership{Type: membershipType}
    if membershipType == "premium" {
        membership.MessageCharLimit = 1000
    } else {
        membership.Type = "standard"
        membership.MessageCharLimit = 100
    }
    return User{Name: name, Membership: membership}
}

/*
Create a SendMessage method for the User struct.
It should take a message string and messageLength int as inputs.
If the messageLength is within the user's message character limit, return the original 
message and true (indicating the message can be sent), otherwise, return an empty string 
and false.
*/

func (u User) SendMessage(message string, messageLength int) (msg string, status bool){
if messageLength <= u.MessageCharLimit {
return message, true
}
return "",false
}
0
Subscribe to my newsletter

Read articles from Rajesh Gurajala directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rajesh Gurajala
Rajesh Gurajala