Structs
A struct groups related values into a single type you name yourself:
package main
import "fmt"
type Point struct {
X float64
Y float64
}
func main() {
p := Point{X: 3, Y: 4}
fmt.Println(p.X, p.Y) // 3 4
p.Y = 10
fmt.Println(p) // {3 10}
}
type Point struct { ... } declares the type; each field has a name and a
type. Fields of the same type can share a line: X, Y float64. Access and
assign fields with a dot. A struct declared with var p Point starts with
every field at its zero value — a usable value from birth, like everything
else in Go.
Literals: name your fields
You can construct a struct value two ways:
a := Point{X: 3, Y: 4} // named fields — do this
b := Point{3, 4} // positional — fragile, avoid
c := Point{Y: 4} // named form can skip fields: X is 0
Prefer the named form. Positional literals silently break (or worse, silently misassign) when someone reorders or adds a field; named literals keep working and read better. The named form also lets you set only what matters and take zero values for the rest.
Structs are values
By now you can guess Go's answer: assigning or passing a struct copies it, fields and all.
func move(p Point) {
p.X = 100 // mutates the copy only
}
p := Point{X: 1, Y: 2}
move(p)
fmt.Println(p) // {1 2} — unchanged
A function that takes a struct can't surprise its caller. So how does a
function "modify" a struct? The idiomatic small-scale answer: it doesn't — it
returns a new one, and the caller assigns the result. q := scaled(p, 2)
says exactly what changed hands. (Pointers offer in-place mutation, and
they're coming in a later chapter — but return-the-new-value is the honest
default and all you need today.)
Comparing structs
If every field is comparable (numbers, strings, booleans...), the struct is
too — == compares field by field:
Point{1, 2} == Point{1, 2} // true
That makes such structs usable as map keys, which is often delightful:
map[Point]string just works.
A teaser: embedding
Structs can embed other structs: listing a type without a field name, as in
type Circle struct { Point; R float64 }, lifts the inner type's fields up
so c.X works directly. It's Go's take on composition-over-inheritance, and
it gets a proper treatment later — for now, just recognize the shape when
you see it.
The exercise below defines a Rect and asks for two functions: area, and a
scale that returns a new scaled rectangle — value semantics in
practice.
Practice
1. Rectangle Area and Scale
BeginnerThe starter defines a rectangle type:
type Rect struct { W, H float64 }Implement two functions on it:
func area(r Rect) float64 func scale(r Rect, f float64) Rectareareturns the rectangle's area, width times height:area(Rect{W: 3, H: 4})is12.scalereturns a newRectwith both sides multiplied byf, leaving the original untouched:scale(Rect{W: 3, H: 4}, 2)isRect{W: 6, H: 8}.
Build the result of
scaleas a struct literal with named fields. Because structs are values,rinsidescaleis already a copy — but the tests still verify the caller's rectangle comes back unchanged, so return a fresh value rather than relying on that.package main import "fmt" // Rect is a rectangle with width W and height H. type Rect struct { W, H float64 } // area returns the area of r (width times height). func area(r Rect) float64 { // TODO: implement return 0 } // scale returns a new Rect with both sides of r multiplied by f. // It does not modify r. func scale(r Rect, f float64) Rect { // TODO: implement return Rect{} } func main() { r := Rect{W: 3, H: 4} fmt.Println(area(r), scale(r, 2)) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.