---
title: "9.4 References and functions"
description: "How to choose function parameter types in Rust: by value for Copy types, &T to read, &mut T to modify in place, and owned types to keep."
url: "https://learnrust.net/chapter-9/references-and-functions/"
last_updated: "2026-06-12"
---

# 9.4 References and functions

In the previous lesson ([9.3](https://learnrust.net/chapter-9/the-borrow-checker-is-your-friend/)), we fixed `shout` by changing its parameter from `String` to `&str`, and the compiler itself suggested the change. But the compiler can only spot ownership a function *doesn't use*. It can't tell you what a function you're about to write *should* ask for. That's a design decision, it comes up every single time you write `fn`, and it has a four-row answer you'll memorize by the end of this lesson.

## The four choices

For any value a function needs, the parameter takes one of four shapes. In rough order of how often you'll reach for each:

```rust
fn area(width: f64, height: f64) -> f64 {
    width * height
}

fn letter_count(text: &str) -> usize {
    text.len()
}

fn add_excitement(text: &mut String) {
    text.push_str("!!!");
}

fn decorated(text: String) -> String {
    text + "!"
}
```

**Copy types: take them by value.** `area`'s floats are lesson [8.5](https://learnrust.net/chapter-8/copy-types/)'s table: numbers, `bool`, `char`, and friends live whole in their box, so passing one hands the function an honest, complete copy. No `&` buys you anything here; a reference to an `i32` is the same size as the `i32`. When the type is Copy, write the plain type and move on.

**Reading a non-Copy value: take `&T`.** `letter_count` only looks, so it asks for a view. The caller keeps ownership, nothing is copied, and the signature promises the no-margin-notes rule from lesson [9.1](https://learnrust.net/chapter-9/references-borrowing-a-value/). For text specifically the view type is `&str` rather than `&String` (lesson 5.4's rule; the final piece of why arrives in lesson 9.7). This is the workhorse row: when in doubt, borrow.

**Modifying the caller's value: take `&mut T`.** `add_excitement` changes a String the caller will keep using, so it asks for the writable loan, and the call site will say `&mut` in plain sight. You've been using the canonical example since chapter 1: `read_line(&mut name)` exists to fill *your* buffer. It modifies in place because the String is yours, you'll trim and parse it next, and you might reuse it for the next read. A writable loan is exactly the relationship.

**Keeping the value: take ownership.** `decorated` uses chapter 8's `+`, which consumes its left side: the function spends `text` to build its answer, so it asks, truthfully, to be *given* the value. Lesson [8.6](https://learnrust.net/chapter-8/ownership-and-functions/) stated the rule from the caller's view, and it's worth restating from the designer's: ask for an owned `String` only when the function keeps or consumes the text. If it merely reads, taking ownership forces every caller into a clone or a funeral.

> **Key insight**
>
> A Rust signature is a contract about ownership, not just types. From `&str` / `&mut String` / `String` alone you know whether a function can change your value and whether you keep it afterward, before reading one line of its body. This is why experienced Rust programmers read an unfamiliar crate's function signatures the way you'd read a menu.

## Out-parameters, and why Rust mostly skips them

There's a fifth pattern, beloved of older C and C++ code, that the `&mut` row makes *possible* in Rust: the **out-parameter**, a writable argument that exists only to carry a result back to the caller. Here's a function that builds an uppercase copy, both ways:

```rust
fn shouted_into(text: &str, out: &mut String) {
    out.clear();
    out.push_str(&text.to_uppercase());
}

fn shouted(text: &str) -> String {
    text.to_uppercase()
}

fn main() {
    let mut loud = String::new();
    shouted_into("goal", &mut loud);
    println!("{loud}");

    let louder = shouted("goal so good");
    println!("{louder}");
}
```

```
GOAL
GOAL SO GOOD
```

Compare the call sites. The out-parameter version makes the caller pre-declare a `mut` variable, build an empty String it doesn't want, and thread `&mut loud` through the call before anything useful occurs. The return version is one line that reads like what it does. learncpp's lesson on this (12.13) reaches the same verdict for C++, and it's house advice here too: results come back in return values; when there are several, a tuple (lesson [4.11](https://learnrust.net/chapter-4/tuples-and-the-unit-type/)) carries them; `&mut` parameters are for functions whose *purpose* is modifying something the caller owns, not for smuggling outputs.

So what separates `read_line` and `add_excitement` (good `&mut`) from `shouted_into` (clumsy `&mut`)? Ask where the value's life is. `add_excitement` changes a String that existed before the call and matters after it: in-place modification, the real thing. `shouted_into`'s `out` was born empty for the call and contains nothing but the result: that's a return value wearing a costume.

> **Best practice**
>
> Choose parameters by what the function does with the value: Copy types by value; `&T` to read; `&mut T` to modify something the caller keeps; owned `T` to keep or consume it. Return results as return values, not through `&mut` out-parameters.

## Quiz time

**Question #1**

Choose the parameter type (`u32`, `&str`, `&mut String`, or `String`) for each described function, and say which row of the table decides it:

a) `fn is_yes(answer: ???) -> bool` returns whether the answer is "y" or "yes"
b) `fn censor(comment: ???)` replaces rude words in a comment the caller keeps using
c) `fn frame(title: ???) -> String` consumes the title, returning it wrapped in `*` characters via `+`
d) `fn stars(count: ???) -> String` returns a row of `count` stars
e) `fn shorten(headline: ???)` truncates a headline in place to 40 characters

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

a) `&str`: reads only. b) `&mut String`: modifies a value the caller keeps. c) `String`: consumes its input (the `+` from chapter 8 spends its left side). d) `u32`: a Copy type, by value. e) `&mut String`: in-place modification again.

If you hesitated between (b)/(e) and (c): the question is whether the caller's variable should still hold the (modified) value afterward, or whether the function swallows it and hands back something new. Both are legitimate designs; the signature is where you commit to one.

</details>

**Question #2**

This function works, but one parameter is an out-parameter in costume. Redesign the signature (no body needed):

```rust
fn full_name(first: &str, last: &str, result: &mut String)
```

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

```rust
fn full_name(first: &str, last: &str) -> String
```

`result` exists only to carry the answer out, so it should be the return value. The inputs stay as `&str` views: reading names doesn't need ownership. Callers go from three lines and a hollow `mut` String down to `let name = full_name(first, last);`.

</details>

**Question #3**

A teammate proposes `fn print_receipt(items: String)` for a function that formats and prints a receipt, arguing "it compiles fine." What goes wrong for callers, and what's the fix?

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

It compiles, but every call *moves* the items String into the function, where it dies after printing. Callers who need their data afterward are forced into `print_receipt(items.clone())`, paying for a copy of text that was only ever read. The signature demands ownership it doesn't use. Fix: `fn print_receipt(items: &str)`, and callers lend with `&items`. (This is patient 3 from last lesson, caught at the design stage instead of the error-message stage, which is the cheaper place to catch it.)

</details>

One row of the table still has an untested edge. A function can *take* a reference, but can it *return* one? Sometimes, and the cases where it can't are the best stories in the chapter. Next lesson: dangling references, or, the bug Rust was built to kill.

## Sitemap

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