RustArena

Step 2 of 6

&str: the type for text parameters

Now you can see what string literals really are. A literal like "hello" is stored in the program's binary, and the value you get is a slice pointing at it: that's why its type is &str, and why it's read-only. (Its full type is &'static str: a borrow that's valid for the whole run of the program. More on that in the lifetimes module.)

So text comes in three shapes: an owned String, a slice of a String, and a literal. A function that takes &String only accepts the first. A function that takes &str accepts all three, because Rust automatically turns a &String into a &str covering the whole text. This automatic conversion is called deref coercion.

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

fn main() {
    let owned = String::from("ferris wins");
    println!("{}", shout(&owned));        // &String becomes &str automatically
    println!("{}", shout(&owned[7..]));   // a slice of it
    println!("{}", shout("gg"));          // a literal
}
FERRIS WINS
WINS
GG

With a &String parameter, the literal doesn't fit:

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

fn main() {
    println!("{}", shout("gg"));
}
error[E0308]: mismatched types
 --> src/main.rs:6:26
  |
6 |     println!("{}", shout("gg"));
  |                    ----- ^^^^ expected `&String`, found `&str`

Prefer &str over &String

Workplace note: for a parameter that only reads text, write &str. It accepts more kinds of argument and loses nothing: every method that reads text (len, chars, contains, to_uppercase, trim, ...) is a str method, which String just makes available. Clippy flags &String parameters with its ptr_arg lint ("writing &String instead of &str involves a new object where a slice will do"). Take an owned String only when the function keeps the text, for example to store it in a vector.

String &str
Owns its text? yes no, borrows it
Can grow or change? yes (if mut) no
Where the text is its own heap block anywhere: a String's heap block, or the program's binary
Size of the value 24 bytes (pointer, length, capacity) 16 bytes (pointer, length)
Get the other one &s, &s[..] or s.as_str() s.to_string() or String::from(s)

Methods that return slices

Several str methods hand you slices of the original text instead of new strings. trim() returns the part without the spaces at both ends, as a &str pointing into the original:

fn main() {
    let input = String::from("   add milk   ");
    let cleaned: &str = input.trim();      // a slice of input: nothing copied
    println!("[{}] {} bytes", input, input.len());
    println!("[{}] {} bytes", cleaned, cleaned.len());
}
[   add milk   ] 14 bytes
[add milk] 8 bytes

split_whitespace(), which you used for reading input, works the same way: each word it gives you is a &str slice of the original line. No copies.

Common mistakes

Your task

The two functions take &String, so the calls with a slice and with a literal don't compile. Change both parameters to &str (don't change main). The program should print:

6 words
first part: 2 words
literal: 2 words
flagged: true
title flagged: false

Challenge 2

Write initials, which returns the first letter of each word, in capitals: "Ada Lovelace" gives "AL", and "grace brewster hopper" gives "GBH". Take a &str so it works with literals and Strings alike. Loop over full_name.split_whitespace() and take each word's first letter with a slice (&word[..1]: the names are plain ASCII). Then uppercase the result.

Challenge 3

Write censor. It returns a copy of text in which every occurrence of banned is replaced by as many * as banned has characters: censor("well darn it", "darn") returns "well **** it". Both parameters are &str, so it works on Strings, slices and literals. (text.replace(from, to) returns a new String, as you saw in the strings intro module.)

Previous: String slices Next: Returning slices: first_word