Structs
How you make your own types by grouping values under one name.
- Write a struct type and create values of it with named fields.
- Predict the output after copying a struct and changing the copy.
- Write a function that changes a struct through a pointer.
What a struct is
A struct is a type you define yourself by grouping other values together under one name. A user has an id, a name and an email; rather than passing three separate variables around, you define a User type that holds all three, and pass one thing.
Structs are how you model whatever your program is about. Go has no classes, so this is the main tool: a struct for the data, and methods (functions attached to that type) for the things it can do.
A struct groups fields
type User struct { ID int Name string Email string}u := User{ID: 1, Name: "sahdev", Email: "s@x.com"} // always name the fieldsfmt.Println(u.Name)Printing a struct
u := User{ID: 1, Name: "sahdev", Email: "s@x.com"}fmt.Printf("%v\n", u) // {1 sahdev s@x.com} — just the valuesfmt.Printf("%+v\n", u) // {ID:1 Name:sahdev Email:s@x.com} — with field namesfmt.Printf("%#v\n", u) // main.User{ID:1, Name:"sahdev", Email:"s@x.com"}fmt.Printf("%T\n", u) // main.UserAssignment copies. Always.
Go has no pass-by-reference. Assigning a struct copies it, and passing one to a function copies it too. When you pass a pointer, you are passing the pointer *by value*. The copy points at the same memory.
a := User{Name: "first"}b := a // a full copy — two independent structsb.Name = "second"fmt.Println(a.Name) // still "first"p := &a // p is a pointer to ap.Name = "changed" // Go dereferences automatically — no (*p).Name neededfmt.Println(a.Name) // "changed"Functions that take a struct
To work with a struct, write an ordinary function that takes it. Take a pointer (*User, from Pointers) when the function must change the original, because a plain User parameter is a copy.
func display(u User) string { return u.Name + " <" + u.Email + ">" }func rename(u *User, n string) { u.Name = n } // pointer: changes the originalu := User{Name: "old", Email: "o@x.com"}rename(&u, "new")fmt.Println(display(u)) // new <o@x.com>Pointers to structs
This builds on Pointers. &User{...} makes a struct and gives you its address in one step; it is the usual way to get a *User. Go also dereferences struct fields for you: with p := &u, you write p.Name, and Go reads it as (*p).Name.
p := &User{Name: "sahdev"} // a new User, and its addressfmt.Println(p.Name) // Go rewrites this as (*p).Namep.Name = "changed" // and this tooA nil-able pointer field
A pointer field can be nil, so it tells "not sent" apart from "sent as empty". A PATCH endpoint needs that: {"title": ""} should clear the title, while {} should leave it alone. JSON shows how the tags work.
type UpdateRequest struct { Title *string `json:"title"` // nil = not provided at all Body *string `json:"body"`}if req.Title != nil { note.Title = *req.Title // they DID send it, even if it is ""}Embedding: a struct inside a struct
You can put one struct inside another with no field name. Its fields are then promoted, so they look like they belong to the outer type.
type Base struct { ID int}type Admin struct { Base // embedded — no field name Level int}a := Admin{Base: Base{ID: 1}, Level: 5}fmt.Println(a.ID) // promoted field — same as a.Base.IDTry it yourself
package mainimport "fmt"type Base struct{ ID int }type User struct { Base // embedded: ID is promoted Name string}func display(u User) string { return u.Name }func rename(u *User, n string) { u.Name = n } // pointer: can mutatefunc main() { u := User{Base: Base{ID: 1}, Name: "first"} fmt.Printf("%+v\n", u) fmt.Println(display(u), u.ID) rename(&u, "second") fmt.Println("after rename:", u.Name) copyOfU := u rename(©OfU, "third") fmt.Println("original is still:", u.Name) // assignment copied it}- A struct groups fields into one type. Always use field names in a literal.
- Assignment and function calls copy a struct. Go has no pass-by-reference.
- Taking a pointer (
*User) lets a function change the original instead of a copy.&User{...}makes one, andp.Namedereferences for you. - A nil-able pointer field (
*string) separates "not sent" from "sent as empty". - Embedding puts one struct inside another; its fields are promoted. Composition, not inheritance.
Book struct (Title, Author, Pages), write func describe(b Book) string and func addPages(b *Book, n int), and print the book with %+v before and after.Book has a Title and Pages. Write Describe(b Book) string, which returns the title and page count like "Go 101 (250 pages)", and AddPages(b *Book, n int), which adds n to the ORIGINAL book's pages. Example: a book with 250 pages after AddPages(&b, 10) has 260.Tab indents. Press Esc, then Tab, to leave the editor.
- 1Copy a struct, change the copy, and print the original.
- 2Write
rename(u User, n string)without a pointer, call it, and print the name. Then switch to*User. - 3Embed a
Base{ID int}in two different structs and printx.IDon both.