Introduction to Structs in Go
In Go, structs are versatile data structures used to group variables together. Structs allow you to model real-world entities clearly and efficiently. By default, when their fields are exported, structs allow read and write access to their data.
Creating a Basic Struct
Let’s start with a basic example of a struct:
package main
import "fmt"
// Defining a struct with exported fields
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name, p.Age) // Outputs: Alice 30
}
The fields in the Person struct (Name and Age) are accessible directly due to their capitalized names, making them exported fields.
Implementing Read-Only Structs
To create a read-only struct in Go, you can define unexported fields and provide only getter methods. This way, outside code cannot modify the values directly.
package main
import "fmt"
// Defining a struct with unexported fields
type person struct {
name string
age int
}
// Getter method for the name field
func (p person) Name() string {
return p.name
}
// Getter method for the age field
func (p person) Age() int {
return p.age
}
func main() {
p := person{name: "Bob", age: 25}
fmt.Println(p.Name(), p.Age()) // Outputs: Bob 25
// Uncommenting the next lines will result in compilation errors
// p.name = "Charlie"
// p.age = 26
}
In this example, fields name and age are unexported by making them lowercase. Getter methods provide read-only access.
Advanced Example with Additional Functionalities
You might wish to include logic within your methods that utilize unexported fields to maintain additional state or derived properties.
package main
import (
"fmt"
"time"
)
type account struct {
owner string
balance float64
startDate time.Time
}
// NewAccount initializes a new account
func NewAccount(owner string, initialBalance float64) account {
return account{owner: owner, balance: initialBalance, startDate: time.Now()}
}
// GetOwner returns the owner of the account
func (a account) GetOwner() string {
return a.owner
}
// BalanceDetails provides access to account balance
func (a account) BalanceDetails() string {
duration := time.Since(a.startDate).Hours() / 24
return fmt.Sprintf("Owner: %s, Balance: %.2f, Opened %v days ago", a.owner, a.balance, int(duration))
}
func main() {
acc := NewAccount("Charlie", 1000.0)
fmt.Println(acc.BalanceDetails()) // Outputs: Details about the account
}
Here, an account struct manages an owner and balance, along with the account's open date. It uses methods to expose data, keeping internal fields secure from modification.
Conclusion
Using unexported fields in Go structs, coupled with getter methods, allows you to create robust APIs by exposing only the necessary parts of your data structures. This facilitates data encapsulation while creating safe, read-only views of complex data types.