Start learning free

LESSON 176 OF 338 · FUNCTIONS & CLASSES

Classes in Go: Structs, Methods and Receivers

Lesson Read the full explanation

Go supports object-oriented programming concepts through structs and methods. A struct can have fields (attributes) and methods, and interfaces define shared behavior.

Struct creates a user-defined data type, which holds its own fields (attributes) and methods, which can be accessed and used by creating a value of that struct type.

Structs are like a blueprint for values outlining possible behaviors and states that every value of a certain type could have.

Structs are defined via the keywords type and struct, like this:

// Define an empty struct Employee
type Employee struct {
}

Values/Instances

A value is an instance of a struct type. A struct is like a blueprint while a value is a copy of the struct with actual data. It's not an idea anymore, it's an actual employee, like a person named "Andrew" who is 30 years old.

You can have many employees to create many different values, but without the struct as a guide, you would be lost, not knowing what information is required.

After a struct is defined, we can create values of that type. There are two ways of creating values: directly and via pointer.

Direct values

Direct values behave the same as local variables. They only exist within the scope where they were created. We create a direct value just like declaring a variable:

// Define an empty struct Employee
type Employee struct {
}

func mainFunction() {
	var employee Employee
}

Pointer values

Pointer values are not bounded by the existence of the scope in which they were created. For example, pointer values created inside a function continue to exist even after the function returns.

A pointer value is created using the & operator that returns a pointer to the newly constructed value:

// Define an empty struct Employee
type Employee struct {
}

func mainFunction() {
	employee := &Employee{}
}

Notice the &Employee{}. This creates a pointer to an Employee struct. We use this pointer to access and manipulate the actual value, just like we use a remote controller for a TV.

Fields/Properties

A struct field is a variable that belongs to a struct. Let's add some fields to our Employee struct:

type Employee struct {
	name string
	age  int
}

func mainFunction() {
	emp1 := Employee{} // direct value

	emp1.name = "Andrew"
	fmt.Println(emp1.name) // prints "Andrew"

	emp2 := &Employee{} // pointer to struct value

	emp2.age = 40
	fmt.Println(emp2.age) // prints 40
}

We added two fields: the string name and the int age. Then, we created two different Employee values emp1 and emp2.

The fields of direct values can be accessed and modified using the dot operator (.), like we did above with emp1.name.

The fields of pointer values are also accessed using the dot operator (.), like we did above with emp2.age — Go automatically dereferences the pointer.

In Go, fields starting with an uppercase letter are exported (accessible from other packages), while lowercase fields are only accessible within the same package.

Constructors

In real life, we want to set the fields specific to each value when creating it. For example, an employee named Andrew of 30 years old and another one named Mary of 25 years old. Here the constructor function comes to rescue.

A constructor function is a regular function used to initialize and return a new struct value. By convention it is named New followed by the struct name.

In Go there is no built-in constructor, so we define our own constructor function:

type Employee struct {
	name string
	age  int
}

// constructor function:
func NewEmployee(name string, age int) Employee {
	return Employee{name: name, age: age}
}

func mainFunction() {
	emp1 := NewEmployee("Andrew", 30) // emp1.name = "Andrew", emp1.age = 30
	fmt.Println(emp1.name)

	emp2 := &Employee{name: "Mary", age: 25} // emp2.name = "Mary", emp2.age = 25
	fmt.Println(emp2.age)
}

The receiver

In Go, methods are functions with a special first parameter called a receiver. The receiver gives the method access to the struct's fields.

The receiver is explicitly declared in the method signature. Inside a method, use the receiver name to access and manipulate the fields of that value, like we do with e.name.

Methods

Methods are functions that belong to the struct type:

type Employee struct {
	name string
	age  int
}

func NewEmployee(name string, age int) Employee {
	return Employee{name: name, age: age}
}

// methods:
func (e Employee) printName() {
	fmt.Println(e.name)
}

func (e Employee) printEmployee() {
	e.printName()
	fmt.Println(e.age)
}

func mainFunction() {
	emp1 := NewEmployee("Andrew", 30)

	emp1.printEmployee() // prints "Andrew", 30

	emp2 := &Employee{name: "Mary", age: 25}

	emp2.printEmployee() // prints "Mary", 25
}

As you can see, we access methods just like we access fields: using the dot syntax (.) for both direct values and pointer values — Go automatically dereferences pointers when calling methods.

The method call uses the fields of the value it was called on. That's why the first printEmployee() prints "Andrew" and 30 and the second prints "Mary" and 25.

Value vs Pointer receivers

In Go, a method receiver can be either a value receiver or a pointer receiver. A value receiver receives a copy of the struct and cannot modify the original. A pointer receiver receives a pointer to the struct and can modify the original.

type Employee struct {
	name string
	age  int
}

func NewEmployee(name string, age int) Employee {
	return Employee{name: name, age: age}
}

// value receiver - reads but does not modify the original:
func (e Employee) printName() {
	fmt.Println(e.name)
}

// pointer receiver - can modify the original:
func (e *Employee) birthday() {
	e.age++
}

func mainFunction() {
	emp1 := NewEmployee("Andrew", 30)

	emp1.printEmployee()

	emp2 := &Employee{name: "Mary", age: 25}

	emp2.printEmployee()
}

Assignment
Follow the Coding Tutorial and let's write some structs.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 5
    Ctrl + Enter to check

    GO · FUNCTIONS & CLASSES

    Classes in Go, explained

    Go has no classes, but it has structs and methods

    If you come from a language with classes, the first surprise is that Go has none. There is no class keyword, no this and no built-in constructor. What Go gives you instead is smaller: a struct groups named fields into one type, and a method is a function that is attached to a type. Together they cover what most programs need from classes.

    Defining a struct

    A struct type is declared with type and struct. Each field is written as its name followed by its type, with no colon and no semicolon:

    type Employee struct {
    	name string
    	age  int
    }
    

    The struct is the blueprint. A value of the type is one concrete employee with actual data.

    Creating values

    A variable of a struct type starts with every field set to its zero value, which is "" for a string and 0 for an int:

    var emp1 Employee
    emp1.name = "Andrew"
    fmt.Println(emp1.name)
    

    Usually you set the fields at creation with a struct literal, naming each field inside braces:

    emp2 := Employee{name: "Mary", age: 25}
    emp3 := &Employee{name: "Ann", age: 41}
    

    The second form, with &, gives a pointer to a new struct. Fields are read and written with the dot operator on both a value and a pointer. Go dereferences the pointer for you, so emp3.age works without any extra syntax.

    Go does not provide constructors. The convention is an ordinary function whose name starts with New, which builds and returns a value:

    func NewEmployee(name string, age int) Employee {
    	return Employee{name: name, age: age}
    }
    

    Methods and receivers

    A method is a function with one extra parameter, the receiver, written in parentheses before the method name. The receiver replaces this, but you name it yourself, and you must always use it to reach the fields:

    func (e Employee) printName() {
    	fmt.Println(e.name)
    }
    

    You call it with the dot: emp2.printName(). Writing just name inside the method would not refer to the field. It would be an undefined variable.

    Value receivers and pointer receivers

    A value receiver, (e Employee), gets a copy of the struct, so changes inside the method do not reach the caller. A pointer receiver, (e *Employee), gets the address of the original and can change it:

    func (e Employee) birthdayCopy() { e.age++ }
    func (e *Employee) birthday()    { e.age++ }
    
    emp := Employee{name: "Andrew", age: 30}
    emp.birthdayCopy()
    fmt.Println(emp.age)
    emp.birthday()
    fmt.Println(emp.age)
    
    30
    31
    

    The first call changed only a copy. The second changed emp itself. Go takes the address of emp automatically for the call. Use a pointer receiver when the method must modify the struct, or when the struct is large and copying it would be wasteful.

    Naming and visibility

    Go controls visibility by the first letter of a name. A name that starts with an uppercase letter is exported, meaning other packages can use it, and a lowercase name is private to its package. That is why the lesson's title and hireMe are lowercase: everything lives in one package.

    The exercise, step by step

    The exercise defines a Job type with one field and one method, then uses them in mainFunction.

    1. Declare the struct with type Job struct, above mainFunction.
    2. Give it a title field of type string.
    3. Declare the hireMe method with a value receiver named job. It prints "You're hired as a " joined with job.title.
    4. In mainFunction, create job with := and a Job literal whose title is "Software Engineer".
    5. Call job.hireMe().

    The complete solution:

    type Job struct {
        title string
    }
    
    func (job Job) hireMe() {
        fmt.Println("You're hired as a " + job.title)
    }
    
    
    
    func mainFunction() {
        job := Job{title: "Software Engineer"}
        job.hireMe()
    }
    

    Running it prints You're hired as a Software Engineer. A value receiver is enough here, because hireMe only reads the field.

    Common mistakes

    • Putting a colon between a field name and its type (title: string), or a comma after it. Go wants title string on its own line.
    • Forgetting the receiver when reading a field. Inside the method, write job.title, not title.
    • Expecting a value receiver to modify the caller's struct. It works on a copy.
    • Searching for a constructor keyword. Use a struct literal, or write a New... function.
    • Leaving a declared variable unused. Go reports an error.

    Key takeaways

    • Structs hold the data, and methods are attached to the type separately, not placed inside it.
    • A receiver is an explicit parameter, and it is how a method reaches its fields.
    • Value receivers copy, pointer receivers modify.
    • Struct literals and New... functions do the job of constructors.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free