Structs and Methods

By Syncfusion®, Inc. · Last updated

Define structs, create and fill them, read, update and compare fields, add methods with value and pointer receivers, write constructors, and use anonymous structs and struct tags.

What Are Structs?

Struct = your own type that groups related values together, each with a name.

Field = one named value inside a struct.

A student has many details:

  • a name (text)
  • a class (text)
  • marks (a number)
  • a hostel flag (true or false)

A struct keeps all of them in one variable.

Simple lesson: a struct is a record with named fields, like one row of a register.

A Real-World Example

Think of the admission form every student fills in.

  • The blank form has the same boxes for everyone: Name, Class, Roll No.
  • Each filled form has different values in those boxes.
  • The school keeps one filled form per student.

Now the mapping:

  • The blank form = the struct type.
  • Each box on the form = a field.
  • A filled form = a struct value, called an instance.

Go has no classes.

  • Structs hold the data.
  • Methods add behaviour to a type.
  • Together, they do the job that classes do in other languages.

What You Will Learn

In this lesson, you will learn:

  1. What structs are, why they are used, and how to define them.
  2. How to create struct values, by position or by field name.
  3. How to read, update, compare and zero struct fields.
  4. What methods are, and the difference between value and pointer receivers.
  5. How to write constructor functions.
  6. Anonymous structs and struct tags.

Struct Fundamentals

Why Structs Are Used

  • Keep related data together. No separate name, class, marks variables to keep in step.
  • Pass one thing to a function instead of five.
  • Make lists of records like []Student. See Collections of Structs.
  • Give the data behaviour with methods.

Defining Structs

type Student struct {
	Name   string
	Class  string
	Marks  int
	Hostel bool
}
  • type Student struct makes a new type named Student.
  • Each line inside is a field: a name, then a type.
  • Fields with the same type can share a line: X, Y int.

Creating Struct Instances

Instance = one value of a struct type.

Make a student

package main

import "fmt"

type Student struct {
	Name  string
	Class string
	Marks int
}

func main() {
	asha := Student{Name: "Asha", Class: "8A", Marks: 91}
	fmt.Println(asha)
	fmt.Printf("%+v\n", asha)
}
{Asha 8A 91}
{Name:Asha Class:8A Marks:91}
  • fmt.Println prints the values in order.
  • %+v also prints the field names. It is handy for checking.

Simple lesson: define a struct with type Name struct { ... }, then fill in an instance with its values.

Struct Initialization

Three ways to fill a struct

Positional Initialization

Positional

package main

import "fmt"

type Point struct {
	X, Y int
}

func main() {
	p := Point{3, 4}
	fmt.Println(p)
}
{3 4}
  • Fine for tiny structs like a point.
  • For bigger structs, use field names.

Positional values must follow the field order exactly.

If someone later adds or reorders fields, every positional literal breaks or, worse, fills the wrong fields. Prefer field names.

Named Field Initialization

Named fields, some left out

package main

import "fmt"

type Student struct {
	Name   string
	Class  string
	Marks  int
	Hostel bool
}

func main() {
	ravi := Student{Name: "Ravi", Marks: 78}
	fmt.Printf("%+v\n", ravi)
}
{Name:Ravi Class: Marks:78 Hostel:false}
  • Class was left out, so it is "".
  • Hostel was left out, so it is false.

Simple lesson: use field names when you fill a struct. Anything you leave out gets its zero value.

Struct Fields

Accessing and Updating Fields

Use a dot: variable.Field.

Read and change fields

package main

import "fmt"

type Student struct {
	Name  string
	Marks int
}

func main() {
	s := Student{Name: "Meena", Marks: 80}
	fmt.Println(s.Name, s.Marks)
	s.Marks += 5
	s.Name = "Meena Iyer"
	fmt.Println(s)
}
Meena 80
{Meena Iyer 85}

Zero Values in Structs

A zero-value struct

package main

import "fmt"

type Book struct {
	Title    string
	Pages    int
	Borrowed bool
}

func main() {
	var b Book
	fmt.Printf("%+v\n", b)
	b.Title = "Panchatantra"
	fmt.Printf("%+v\n", b)
}
{Title: Pages:0 Borrowed:false}
{Title:Panchatantra Pages:0 Borrowed:false}

Structs Are Copied

A struct is a value type, like an array. See Arrays.

Copying a struct

package main

import "fmt"

type Student struct {
	Name  string
	Marks int
}

func main() {
	a := Student{"Asha", 91}
	b := a
	b.Marks = 50
	fmt.Println(a.Marks, b.Marks)
}
91 50
  • b := a copies every field.
  • To share one struct, use a pointer. See Pointers.

Struct Comparison

Two structs can be compared with == when all their fields can be compared.

Compare structs

package main

import "fmt"

type Seat struct {
	Row, Col int
}

func main() {
	mine := Seat{2, 5}
	yours := Seat{2, 5}
	fmt.Println(mine == yours)
	fmt.Println(mine == Seat{3, 5})
}
true
false
  • == is true when every field is equal.
  • A struct with a slice or map field cannot use ==.

Simple lesson: read and change fields with a dot, remember that assigning copies the struct, and compare with ==.

Methods

What Are Methods?

Method = a function that belongs to a type.

Receiver = the value the method is called on. It is written before the method name.

func (s Student) Report() string {
	return s.Name + ": " + strconv.Itoa(s.Marks)
}
  • (s Student) is the receiver.
  • You call it with a dot: asha.Report().

Value Receivers

Value receiver = (s Student). The method gets a copy.

  • Use it when the method only reads the struct.

Pointer Receivers

Pointer receiver = (s *Student). The method gets a pointer.

  • Use it when the method changes the struct.
  • Also use it for big structs, to avoid copying.

Press Play and compare the two methods.

Value receiver vs pointer receiver

A report card with methods

package main

import "fmt"

type Student struct {
	Name  string
	Marks int
}

func (s Student) Grade() string {
	if s.Marks >= 90 {
		return "A"
	}
	if s.Marks >= 75 {
		return "B"
	}
	return "C"
}

func (s *Student) AddGrace(n int) {
	s.Marks += n
}

func main() {
	ravi := Student{"Ravi", 72}
	fmt.Println(ravi.Name, ravi.Grade())
	ravi.AddGrace(5)
	fmt.Println(ravi.Name, ravi.Marks, ravi.Grade())
}
Ravi C
Ravi 77 B
  • ravi.AddGrace(5) works even though ravi is not a pointer. Go writes (&ravi).AddGrace(5) for you.

Easy way to remember

  • Method reads → value receiver (s Student)
  • Method changes → pointer receiver (s *Student)
  • If one method of a type needs a pointer, give all its methods pointer receivers, for consistency.

Simple lesson: a method is a function with a receiver, and a pointer receiver lets it change the struct.

Constructors

Factory Functions

Go has no special constructor keyword.

Constructor (or factory function) = a normal function, usually named NewX, that builds and returns a ready-to-use value.

NewStudent

package main

import "fmt"

type Student struct {
	Name  string
	Class string
	Marks int
}

func NewStudent(name, class string) *Student {
	return &Student{Name: name, Class: class, Marks: 0}
}

func main() {
	s := NewStudent("Kavya", "8C")
	s.Marks = 88
	fmt.Printf("%+v\n", *s)
}
{Name:Kavya Class:8C Marks:88}
  • NewStudent fills in the fields and gives back a pointer.
  • Returning a pointer to a local value is safe in Go. The value lives on as long as it is used.

Initialization Patterns

  • Set defaults that are not zero values, like Class: "8A".
  • Check inputs, and return an error if they are wrong. See Error Handling.
  • Return a pointer (*T) when the value will be changed by methods.

A constructor with a default and a check

package main

import (
	"errors"
	"fmt"
)

type Locker struct {
	Number int
	Owner  string
}

func NewLocker(number int, owner string) (*Locker, error) {
	if number < 1 || number > 200 {
		return nil, errors.New("locker number must be 1 to 200")
	}
	if owner == "" {
		owner = "unassigned"
	}
	return &Locker{Number: number, Owner: owner}, nil
}

func main() {
	l, err := NewLocker(12, "")
	fmt.Println(*l, err)
	_, err = NewLocker(500, "Ravi")
	fmt.Println(err)
}
{12 unassigned} <nil>
locker number must be 1 to 200

Simple lesson: write a NewX function to build a value with good defaults and checks.

Anonymous Structs

Inline Structs

Anonymous struct = a struct with no type name, made and used in one place.

An anonymous struct

package main

import "fmt"

func main() {
	topper := struct {
		Name  string
		Marks int
	}{"Kabir", 98}
	fmt.Println(topper.Name, topper.Marks)

	scores := []struct {
		Subject string
		Marks   int
	}{
		{"Maths", 92},
		{"Science", 88},
	}
	for _, s := range scores {
		fmt.Println(s.Subject, s.Marks)
	}
}
Kabir 98
Maths 92
Science 88
  • Good for small, one-time groupings, like test data.
  • If you use the shape in more than one place, give it a name with type.

Simple lesson: an anonymous struct is a quick, nameless record for one-time use.

Struct Tags

Struct Tag Fundamentals

Struct tag = extra text after a field, in back-quotes. Packages read it to know how to handle the field.

type Student struct {
	Name  string `json:"name"`
	Marks int    `json:"marks"`
}
  • Tags do nothing by themselves.
  • Packages like encoding/json read them.

JSON Tags

JSON field names from tags

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name   string `json:"name"`
	Class  string `json:"class"`
	Marks  int    `json:"marks"`
	Remark string `json:"remark,omitempty"`
}

func main() {
	s := Student{Name: "Asha", Class: "8A", Marks: 91}
	data, _ := json.Marshal(s)
	fmt.Println(string(data))
}
{"name":"Asha","class":"8A","marks":91}
  • json:"name" = use name as the key, not Name.
  • omitempty = leave the field out when it is empty.
  • You will learn JSON fully in JSON.

Custom Tags

Your own code can read any tag with the reflect package. You will learn reflect in Reflection.

Read a custom tag

package main

import (
	"fmt"
	"reflect"
)

type Form struct {
	Name  string `label:"Student name"`
	Phone string `label:"Parent phone"`
}

func main() {
	t := reflect.TypeOf(Form{})
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		fmt.Println(f.Name, "→", f.Tag.Get("label"))
	}
}
Name → Student name
Phone → Parent phone
  • f.Tag.Get("label") reads the value of the label tag.
  • Validation and database packages use tags the same way.

Simple lesson: a struct tag is a label for other code, most often json:"name".

TL;DR

  1. Struct = type Student struct { Name string; Marks int }.
  2. Create = Student{Name: "Asha", Marks: 91} (named), Student{"Asha", 91} (positional), var s Student (zero).
  3. Fields = s.Name. Assigning copies the struct. == compares all fields.
  4. Method = func (s Student) Grade() string.

    • Value receiver = reads a copy.
    • Pointer receiver = (s *Student), can change the struct.
  5. Constructor = func NewStudent(...) *Student, with defaults and checks.
  6. Anonymous struct = struct{ ... }{ ... } for one-time use.
  7. Struct tag = `json:"name"`, read by packages like encoding/json.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
A value receiver that changes a fieldChanges stickThe method changes a copyThe struct stays the same.
Positional values in the wrong orderGo matches by typeGo matches by positionFields get the wrong values.
Leaving out a field in positional formMissing ones become zeroPositional needs every fieldtoo few values in struct literal
b := a to share a structIt sharesIt copiesChanges to b are not seen in a.
Lowercase field with encoding/jsonAll fields are savedOnly exported (Capital) fieldsThe field is missing in the JSON.
== on a struct with a slice fieldAll structs compareOnly if every field is comparableA build error.
A tag with spaces: json: "name"Spaces are fineWrite json:"name" exactlyThe tag is ignored.

Check Yourself

Exercises

What to Remember

  • A struct groups named fields into your own type.
  • Fill structs with field names. Missing fields get zero values.
  • Assigning a struct copies it. == compares all fields.
  • Methods have receivers. Use a pointer receiver to change the struct.
  • Write NewX functions for defaults and checks.
  • Struct tags like json:"name" tell packages how to treat a field.

Next: Composition and Embedding. You will learn how Go builds bigger types from smaller ones, instead of using inheritance.