---
title: "10.2 Defining and instantiating structs"
description: "How to define a struct's fields, create an instance, read and write fields with dot notation, and why Rust makes the whole struct mutable, not individual fields."
url: "https://learnrust.net/chapter-10/defining-and-instantiating-structs/"
last_updated: "2026-06-13"
---

# 10.2 Defining and instantiating structs

Last lesson made the case for structs. This one makes one and uses it.

## Defining the struct

A struct definition lists the fields the type holds, each with a name and a type:

```rust
struct Point {
    x: f64,
    y: f64,
}
```

This is a *definition*, not a value. It creates no data and reserves no memory; it teaches the compiler what a `Point` is, the same way `fn` teaches it what a function is. Put it at the top level of a file, outside `main`, where types usually live. Each field gets an explicit type, for the same reason function parameters do (lesson [2.3](https://learnrust.net/chapter-2/parameters-and-arguments/)): the compiler refuses to guess, and the guarantee that every `Point` has exactly two `f64` fields is worth typing out.

Note the trailing comma after the last field. Rust allows it everywhere and rustfmt adds it, so that inserting a new field later is a one-line diff. You'll see it throughout the course.

## Creating an instance

To make an actual `Point`, name the type and give every field a value:

```rust
fn main() {
    let origin = Point { x: 0.0, y: 0.0 };
    let corner = Point { x: 3.0, y: 4.0 };
    println!("corner is at ({}, {})", corner.x, corner.y);
}
```

```
corner is at (3, 4)
```

An **instance** is a concrete value of a struct type, the way `5` is a concrete value of `i32`. The syntax reads like the definition turned inside out: where the definition said `x: f64` (a name and a type), the instance says `x: 0.0` (a name and a value). You must supply *every* field. Leave one out and the program is refused:

```rust
let p = Point { x: 1.0 };
```

```
error[E0063]: missing field `y` in initializer of `Point`
 --> src/main.rs:7:13
  |
7 |     let p = Point { x: 1.0 };
  |             ^^^^^ missing `y`
```

This is lesson [1.4](https://learnrust.net/chapter-1/variables-mutability-initialization/)'s rule wearing a bigger coat: Rust won't let you read an uninitialized value, and a half-built `Point` is exactly that. There's no such thing as a `Point` with an undecided `y`. (Chapter 10.6 shows the clean way to supply sensible starting values so you don't repeat them at every call.)

## Reading fields with dot notation

Reach into an instance with a dot and the field name, which you've already seen above and which behaves just like tuple access from lesson [4.11](https://learnrust.net/chapter-4/tuples-and-the-unit-type/), except the parts have names instead of numbers:

```rust
let corner = Point { x: 3.0, y: 4.0 };
let distance = (corner.x * corner.x + corner.y * corner.y).sqrt();
println!("{distance}");
```

```
5
```

`corner.x` is an `f64`, usable anywhere an `f64` is. The names are the point: `corner.x` says what it is, where a tuple's `corner.0` makes you remember that slot 0 was the x coordinate. Compare a struct field to a tuple element and the case for naming makes itself.

## Mutating fields

Reading is one thing; changing a field is where Rust's mutability rule from lesson [1.4](https://learnrust.net/chapter-1/variables-mutability-initialization/) reappears, with a twist worth pausing on. To change any field, the *whole binding* must be `mut`:

```rust
fn main() {
    let mut player = Point { x: 0.0, y: 0.0 };
    player.x = 10.0;
    player.y = 5.0;
    println!("({}, {})", player.x, player.y);
}
```

```
(10, 5)
```

Drop the `mut` and assigning to `player.x` is refused:

```
error[E0594]: cannot assign to `player.x`, as `player` is not declared as mutable
 --> src/main.rs:3:5
  |
3 |     player.x = 10.0;
  |     ^^^^^^^^^^^^^^^ cannot assign
```

Here's the twist. Rust has **no per-field mutability**. You cannot declare that `x` is changeable while `y` is frozen; mutability is a property of the binding, not of individual fields. Either the whole `player` is mutable or none of it is.

> **Key insight**
>
> Mutability in Rust attaches to *bindings*, not to data. A field isn't "a mutable field"; it's a field of a value you happen to hold mutably. Move that same value into an immutable binding (or borrow it with a plain `&`) and every field is read-only again. This is the lesson [1.4](https://learnrust.net/chapter-1/variables-mutability-initialization/) rule, consistent all the way up: the variable decides, not the type.

If you find yourself wishing one field could be frozen while the rest move, that's usually a sign the frozen field belongs to a different type, or that the struct's fields should be private and changed only through methods (chapter 13 makes fields private; this chapter's methods, lesson [10.5](https://learnrust.net/chapter-10/methods-and-impl-blocks/), are how you guard them).

## Structs in functions

A struct is a type like any other, so it goes in and out of functions the ordinary way, and the rules from chapters 8 and 9 apply with zero new vocabulary:

```rust
fn distance_from_origin(p: &Point) -> f64 {
    (p.x * p.x + p.y * p.y).sqrt()
}

fn main() {
    let corner = Point { x: 3.0, y: 4.0 };
    println!("distance: {}", distance_from_origin(&corner));
    println!("still own it: ({}, {})", corner.x, corner.y);
}
```

```
distance: 5
still own it: (3, 4)
```

`distance_from_origin` takes `&Point`, so the call borrows `corner` rather than moving it, exactly as `&String` did in lesson [9.1](https://learnrust.net/chapter-9/references-borrowing-a-value/). `corner` is still the owner afterward, so `main` keeps reading its fields on the next line. Taking `&Point` instead of `Point` is the same parameter decision from lesson [9.4](https://learnrust.net/chapter-9/references-and-functions/), made for the same reason: the function only looks, so it borrows.

## Quiz time

**Question #1**

Write a `struct Rectangle` with `width` and `height` fields (both `f64`), then create one that is 4.0 wide and 2.5 tall and print its area.

<details class="solution">
<summary>Show solution</summary>

```rust
struct Rectangle {
    width: f64,
    height: f64,
}

fn main() {
    let r = Rectangle { width: 4.0, height: 2.5 };
    println!("area: {}", r.width * r.height);
}
```

Prints `area: 10`. Rectangles return as the chapter's running example in lesson [10.9](https://learnrust.net/chapter-10/project-rectangles/).

</details>

**Question #2**

This program is refused. Name the error and fix it.

```rust
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p = Point { x: 1.0, y: 2.0 };
    p.x = 9.0;
    println!("{}", p.x);
}
```

<details class="solution">
<summary>Show solution</summary>

E0594: `p` isn't declared mutable, so its field can't be assigned. Fix: `let mut p = ...`. There's no way to make just `x` mutable; mutability is a property of the binding `p`, not of the field.

</details>

**Question #3**

True or false: you can create a `Point { x: 1.0 }` and fill in `y` later. Explain.

<details class="solution">
<summary>Show solution</summary>

False. Every field must be given a value when the instance is created (E0063, missing field `y`). A struct with an unset field would be an uninitialized value, which lesson [1.4](https://learnrust.net/chapter-1/variables-mutability-initialization/) forbids. If you want default starting values, lesson [10.6](https://learnrust.net/chapter-10/associated-functions-and-constructors/) shows the idiomatic way.

</details>

Next lesson trims the repetition out of building instances: field shorthand when a variable already has the right name, and update syntax for "the same as this one, but with two fields changed".

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
