Structs and Methods
Scope: grouping data with structs, attaching behavior with methods, and how Go does "objects" without classes. All examples were compiled and run.
Structs: named bundles of data
type User struct {
Name string
Age int
Active bool
}
u := User{Name: "Ada", Age: 36, Active: true}
fmt.Println(u.Name) // Ada
type User structdefines the shape.- Field names are capitalized here, so they're exported — another
package could read
u.Name. Lowercase fields stay private. - Initialize with field names (
User{Name: ...}) — order doesn't matter and it's self-documenting. The positional form (User{"Ada", 36, true}) exists but is fragile; prefer names.
Methods: functions with a receiver
func (u User) Greeting() string {
return "hi, I'm " + u.Name
}
fmt.Println(u.Greeting())
The (u User) part is the receiver — it attaches the
function to the User type. u.Greeting() reads naturally.
Value vs pointer receivers
func (u User) Birthday() { // value receiver: works on a copy
u.Age++ // modifies the copy — lost!
}
func (u *User) Birthday() { // pointer receiver: modifies the original
u.Age++
}
- Value receiver
(u User)— the method gets a copy. Good for read-only methods on small structs. - Pointer receiver
(u *User)— the method can modify the original. Required when the method changes the struct.
Rule of thumb: if any method on a type needs a pointer
receiver, give them all pointer receivers for consistency. Go
handles the & automatically at the call site — u.Birthday()
works whether u is a value or pointer.
Constructors: just functions
Go has no constructors. You write a function:
func NewUser(name string, age int) *User {
return &User{Name: name, Age: age, Active: true}
}
u := NewUser("Ada", 36)
The New prefix is convention, not syntax. Returning a pointer
(*User) avoids copying.
Embedding: Go's answer to inheritance
Instead of inheritance, Go embeds one struct inside another:
type Employee struct {
User // embedded — Employee gets User's fields and methods
EmployeeID string
}
e := Employee{User: User{Name: "Ada", Age: 36}, EmployeeID: "E42"}
fmt.Println(e.Name) // "Ada" — promoted from User
fmt.Println(e.Greeting()) // works too
Composition, not inheritance. Employee has a User; it
doesn't derive from one. You can't override methods — if both
types define Greeting, the outer one wins, explicitly. This
avoids entire categories of inheritance headaches.
No classes, and that's fine
Go's model is deliberately flat:
| Other languages | Go |
|---|---|
| Class with fields + methods | Struct + methods |
| Inheritance | Embedding (composition) |
| Constructor | NewXxx function |
| public/private keywords | Capitalization |
Check
- [ ] You can define a struct, build one, and add a method
- [ ] You can explain value vs pointer receivers and pick correctly
- [ ] You can explain embedding vs inheritance in one sentence
Next: Interfaces.