Structs: Go's Plain Memory vs Rust's Ownership
Structs bundle named fields into a custom type. Use them when tuples or maps collapse under many values. The footgun is assuming the same syntax means the same rules: Go zero-values fields silently, while Rust demands explicit initialization unless you derive…
WHY IT EXISTS: Before structs, related data was passed as parallel arrays or loose tuples that decayed into unreadable parameter lists. Structs exist to give a named shape to a bundle of values so the compiler can enforce field access and memory layout. They are the foundational unit of user-defined types in both Go and Rust because neither language relies on classical inheritance for code reuse.
THE MENTAL MODEL: Think of a struct as a row in a strictly typed spreadsheet. Each column has a name and a type, and the whole row moves through your program as one value. In Go, that row is a plain contiguous block of memory with compiler-inserted padding to align fields. In Rust, the same row is also a memory block, but it is governed by ownership rules: moving the struct moves all its fields, borrowing it creates references with lifetimes, and you can override memory layout with attributes like repr(C).
HOW IT WORKS: In Go, you declare a struct with the type keyword and attach methods using receiver syntax. The compiler lays out fields in declaration order and inserts padding for alignment; you cannot reorder or pack them without unsafe tricks. A new struct value is automatically zero-valued, meaning every numeric field becomes zero and every pointer becomes nil. In Rust, you declare a struct with the struct keyword and implement behavior in impl blocks. Fields must be fully initialized before the value is considered valid. Rust allows derived traits such as Default, Clone, and Debug to generate boilerplate automatically. Method resolution uses immutable, mutable, or owned self receivers, and the borrow checker verifies that references to struct fields do not outlive the struct itself.
WHEN TO USE IT: Use a struct when a group of values travels together across multiple functions, when you need named fields for clarity, or when you want to define methods on a custom type. They replace maps when you require compile-time field names and replace tuples when the arity exceeds two or three. In Rust, structs are also the gateway to traits; in Go, they are the only way to attach methods to user-defined data.
WHEN NOT TO USE IT: Do not use a struct as a bag of unrelated globals. If two fields are never accessed together, they belong in separate types. In Go, avoid embedding structs to simulate inheritance; embedding promotes methods and fields but does not create an is-a relationship, and shadowing rules can surprise you. In Rust, do not stuff owned heap data into a struct if you only need temporary views; references inside structs introduce lifetime parameters that infect every call site.
ONE CANONICAL EXAMPLE: A configuration parser reads host, port, and timeout from environment variables. In Go, you define a Config struct with string and time.Duration fields, then write a Load method that populates it; missing fields are harmless because they default to empty strings or zero durations. In Rust, you define a Config struct with String and u64 fields and derive Default; a builder function or the Default trait fills in sensible values, and the compiler refuses to construct the struct unless every field is accounted for, catching missing initialization at compile time rather than runtime.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.