Structures & Enumerations

Structures let you group several related pieces of data together under one named type. Enumerations let you define a type restricted to a specific, named set of possible values. Both make code more expressive and less error-prone than using loose, disconnected variables or 'magic numbers.'

A structure (struct) bundles multiple related fields together into a single named type — for example, a Point structure might hold an x and a y coordinate together. An enumeration (enum) defines a type that can only hold one of a fixed, named set of values — for example, a Day type that can only be Monday, Tuesday, ..., Sunday.

Without structures, related data (like a point's x and y) would have to be tracked as separate, disconnected variables, easy to accidentally mix up or forget to keep in sync. Without enumerations, code often falls back to arbitrary numbers (0 for Monday, 1 for Tuesday) that are far less readable and far easier to use incorrectly than clearly named values.

Representing a 2D point, a person's contact record (name, email, phone), a network packet's fields, a set of valid states in a state machine (Idle, Running, Paused, Stopped), or the days of the week, suits (or specific card ranks/suits) — anywhere data naturally groups together or is naturally restricted to a fixed set of choices.

A structure is like a labeled form with several fields to fill in — a single piece of paper holding a person's name, address, and phone number together, rather than three separate, easily-misplaced sticky notes. An enumeration is like a multiple-choice question with a fixed list of allowed answers — you can pick 'Red,' 'Green,' or 'Blue,' but never something outside that predefined list.

struct Point groups x and y into a single type, so p.x and p.y always travel together as one logical unit. enum Day restricts today to one of exactly seven named values, rather than an arbitrary, unchecked integer.

In many languages, a struct's fields are stored in memory contiguously, in roughly the order they're declared (with possible padding for alignment, covered in the future Memory & Program Execution module) — this differs from an object in some object-oriented languages, which may additionally carry hidden metadata (like a pointer to type/class information for dynamic dispatch).

Enums under the hood are frequently represented as plain integers internally (0, 1, 2, ... for each named value in declaration order), even though the source code refers to them by meaningful names — this is why, in some languages, an enum value can be compared to or converted from an integer, while other languages deliberately restrict this to preserve type safety.

Some modern languages support 'algebraic data types' or 'enums with associated data' (Rust's enum, Swift's enum) — a more powerful generalization where each named variant of an enum can additionally carry its own distinct set of associated fields, unlike a traditional C-style enum which is just a named integer.

Structures (and similar record-like types) are often used as simple data containers, contrasted with classes/objects which typically also bundle in behavior (methods) alongside data — though this distinction varies significantly by language, and some languages treat structs and classes as nearly interchangeable except for default value/reference semantics.

Forgetting to initialize all of a struct's fields, leaving some containing garbage/undefined values in unsafe languages like C, which then cause hard-to-trace bugs later when that field is read.

Comparing struct instances for equality using a default comparison that only checks reference/pointer equality (are these the exact same object in memory) when field-by-field value equality was actually intended, or vice versa.

Using raw integers or strings instead of an enum for a value that's logically restricted to a fixed set of options, losing both compile-time safety (a typo like "Mnday" isn't caught) and code readability.

Assuming every language's enum values are silently interchangeable with integers — some languages (and static type checkers) deliberately forbid this by default specifically to prevent passing an unrelated, coincidentally-matching integer where a specific enum value was intended.

Using a struct/record type to group a function's several related return values (like a Result structure holding both a status code and a message) instead of returning several loosely related, positional values.

Using an enum to represent a fixed, known set of states in a state machine — a traffic light's Red/Yellow/Green, or a network connection's Disconnected/Connecting/Connected/Error — making invalid states impossible to represent, rather than merely discouraged by convention.

Choosing Rust- or Swift-style enums with associated data specifically to model a value that is genuinely 'one of several distinct shapes,' rather than forcing all variants to share exactly the same fields.

Struct memory layout and padding A compiler typically lays a struct's fields out in memory in declaration order, but may insert padding bytes between fields (or after the last field) so each field starts at an address satisfying its own alignment requirement, and so the overall struct's size is a multiple of its largest field's alignment. This means simply reordering a struct's field declarations can change its total memory footprint, without changing its logical content at all — covered fully in the future Memory & Program Execution module.

Tagged unions (algebraic data types) under the hood An enum with associated data (as in Rust) is typically implemented as a 'tagged union': a small integer tag identifying which variant is currently active, plus enough storage to hold the largest variant's associated data, reused (overlapping in memory) across all variants since only one is ever active for a given value at a time. This is a distinct memory strategy from a plain C-style enum, which needs no such overlapping storage since it carries no per-variant data at all.

Languages with rich enum/pattern-matching support (Rust, Swift, modern TypeScript with discriminated unions) can have their compiler statically verify that every possible enum variant is explicitly handled somewhere in a match/switch expression, refusing to compile otherwise — turning 'did I forget to handle a case' from a runtime risk into a compile-time guarantee, a significant safety benefit as an enum's variants evolve over a codebase's lifetime.

What's the main advantage of using an enum instead of raw integers to represent a fixed set of states?

Type safety and readability — the compiler (in statically typed languages) can catch invalid values or typos at compile time, code that uses named values like RUNNING is far more self-documenting than a bare 2, and it becomes impossible to accidentally pass a value outside the intended set.

Why can reordering a struct's fields change its total size in memory, even though the data is logically unchanged?

Because compilers often insert padding bytes between fields to satisfy each field's memory-alignment requirements. Different field orderings can require different amounts of padding to keep every field properly aligned, changing the struct's overall size even though its logical content is identical.

What is a tagged union, and what language feature is it typically used to implement?

A memory layout technique combining a small tag (identifying which variant is active) with overlapping storage sized for the largest possible variant — commonly used to implement enums with associated data (algebraic data types), such as Rust's enum, where each variant can carry different data but only one variant's data is ever active for a given value.

struct Point {
    int x;
    int y;
};

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

struct Point p = {3, 4};
enum Day today = WEDNESDAY;