Lesson 1
Go Structs and Methods: An Introduction
Lesson Overview

Hello, there! Today, we’ll dive into Go's core data type: structs. Structs in Go, along with struct methods, serve a role similar to classes in object-oriented programming. We'll explore structs, how they encapsulate data, and how we can manipulate this data using methods.

Go Structs Refresher

To fully grasp Go's structs, think of them as blueprints for creating complex data types by grouping different pieces of related information. Unlike arrays or slices, which hold data of the same type, structs allow you to mix various data types. This powerful feature is why they're akin to custom records or tailored objects, making them very versatile in developing sophisticated programs.

In addition to organizing your data, structs are instrumental in modularizing your code and breaking down complex programs into manageable pieces. This enhances code reusability and readability, providing a clear way to model real-life entities—just like how a GameCharacter may have fields for attributes like health and strength.

Defining Go Structs

In Go, to define a struct, use the type keyword followed by the struct name and its fields. For instance, considering the GameCharacter example:

Go
1package main 2 3import "fmt" 4 5// GameCharacter struct definition 6type GameCharacter struct { 7 name string 8 health int 9 strength int 10} 11
Struct Fields

Fields in Go structs hold data related to each struct instance, like the name, health, and strength in the GameCharacter struct. Initialize fields by passing values as a composite literal or explicitly setting them after creation.

Go
1package main 2 3import "fmt" 4 5// GameCharacter struct with fields 6type GameCharacter struct { 7 name string 8 health int 9 strength int 10} 11 12func main() { 13 // Initialize fields using composite literal 14 character := GameCharacter{name: "Hero", health: 100, strength: 20} 15 fmt.Println(character.name) // prints: Hero 16 fmt.Println(character.health) // prints: 100 17 fmt.Println(character.strength) // prints: 20 18 19 // Update field values 20 character.health = 90 21 fmt.Println(character.health) // prints: 90 22}

Accessing struct fields involves the dot (.) operator, and initialization occurs using composite literals or individual assignments.

Understanding Methods: Receiver Functions

Differently from other OOP languages, in Go methods are simply functions with a receiver argument. Receiver functions in Go feature the special receiver parameter, which specifies the struct type a function is associated with. This receiver must be defined between the func keyword and the method name. It's analogous to the self parameter in Python or this in Java/C++; however, differently from such languages the receiver comes in two flavors:

  • Value Receiver (g GameCharacter): This creates a copy of the data, not modifying the original.
  • Pointer Receiver (g *GameCharacter): Allows direct modification of the original struct instance and passes memory addresses, making it more memory efficient.
Go
1package main 2 3import "fmt" 4 5// GameCharacter struct definition 6type GameCharacter struct { 7 name string 8 health int 9 strength int 10} 11 12// The attack method allows one character to reduce another's health 13func (g *GameCharacter) attack(other *GameCharacter) { 14 other.health -= g.strength 15} 16 17func main() { 18 character1 := GameCharacter{name: "Hero", health: 100, strength: 20} 19 character2 := GameCharacter{name: "Villain", health: 80, strength: 15} 20 21 fmt.Println(character2.health) // prints: 80 22 character1.attack(&character2) // Hero attacks Villain 23 fmt.Println(character2.health) // prints: 60 24}

In this example, we associate a method called attack to the GameCharacter struct, providing it with with the ability to attack another character. It utilizes a pointer receiver, allowing us to modify the original other character's health field by reducing it based on the attacking character's strength. By using a pointer receiver (g *GameCharacter), we directly manipulate the memory of the other instance, ensuring the changes are reflected externally.

Practical Example: Handling a Bank Account

Now, let's implement a BankAccount struct to demonstrate modeling a real-world entity, showcasing attributes like the account holder's name and balance, and methods for depositing and withdrawing funds.

Go
1package main 2 3import ( 4 "fmt" 5) 6 7// BankAccount struct with fields 8type BankAccount struct { 9 holderName string 10 balance float64 11} 12 13// Method to deposit money into the account 14func (b *BankAccount) deposit(amount float64) { 15 if amount > 0 { 16 b.balance += amount 17 fmt.Printf("%.2f deposited. New balance: %.2f\n", amount, b.balance) 18 } else { 19 fmt.Println("Deposit amount must be positive.") 20 } 21} 22 23// Method to withdraw money from the account 24func (b *BankAccount) withdraw(amount float64) { 25 if amount > 0 && amount <= b.balance { 26 b.balance -= amount 27 fmt.Printf("%.2f withdrawn. Remaining balance: %.2f\n", amount, b.balance) 28 } else { 29 fmt.Println("Insufficient balance for the withdrawal or amount is not positive.") 30 } 31} 32 33func main() { 34 account := BankAccount{holderName: "Alex", balance: 1000} 35 36 // Perform some transactions 37 account.deposit(500) 38 account.withdraw(200) 39 fmt.Printf("Final balance in %s's account: %.2f\n", account.holderName, account.balance) 40}

In the above code snippet, the BankAccount struct effectively models a real-world bank account with fields like holderName and balance. The provided method deposit accepts a positive amount and updates the balance accordingly. Similarly, the withdraw method deducts a valid amount from the balance, demonstrating pointer receivers in action for modifying the struct's internal state. This example illustrates encapsulation of behaviour within the struct by combining data representation with methods for operations relevant to the entity being modeled.

Lesson Summary and Practice

Well done exploring Go's structs and methods, and how they allow neat data organization and manipulation. Utilizing structs helps maintain clean and efficient source code. Try expanding your knowledge by creating new structs and defining methods to see how you can model real-world entities in Go. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.