1. 1. Introduction
  2. 2. Getting Started
  3. 3. Tutorial: Guessing Game
  4. 4. Syntax and Semantics
    1. 4.1. Variable Bindings
    2. 4.2. Functions
    3. 4.3. Primitive Types
    4. 4.4. Comments
    5. 4.5. if
    6. 4.6. Loops
    7. 4.7. Vectors
    8. 4.8. Ownership
    9. 4.9. References and Borrowing
    10. 4.10. Lifetimes
    11. 4.11. Mutability
    12. 4.12. Structs
    13. 4.13. Enums
    14. 4.14. Match
    15. 4.15. Patterns
    16. 4.16. Method Syntax
    17. 4.17. Strings
    18. 4.18. Generics
    19. 4.19. Traits
    20. 4.20. Drop
    21. 4.21. if let
    22. 4.22. Trait Objects
    23. 4.23. Closures
    24. 4.24. Universal Function Call Syntax
    25. 4.25. Crates and Modules
    26. 4.26. `const` and `static`
    27. 4.27. Attributes
    28. 4.28. `type` aliases
    29. 4.29. Casting between types
    30. 4.30. Associated Types
    31. 4.31. Unsized Types
    32. 4.32. Operators and Overloading
    33. 4.33. Deref coercions
    34. 4.34. Macros
    35. 4.35. Raw Pointers
    36. 4.36. `unsafe`
  5. 5. Effective Rust
    1. 5.1. The Stack and the Heap
    2. 5.2. Testing
    3. 5.3. Conditional Compilation
    4. 5.4. Documentation
    5. 5.5. Iterators
    6. 5.6. Concurrency
    7. 5.7. Error Handling
    8. 5.8. Choosing your Guarantees
    9. 5.9. FFI
    10. 5.10. Borrow and AsRef
    11. 5.11. Release Channels
    12. 5.12. Using Rust without the standard library
  6. 6. Nightly Rust
    1. 6.1. Compiler Plugins
    2. 6.2. Inline Assembly
    3. 6.3. No stdlib
    4. 6.4. Intrinsics
    5. 6.5. Lang items
    6. 6.6. Advanced linking
    7. 6.7. Benchmark Tests
    8. 6.8. Box Syntax and Patterns
    9. 6.9. Slice Patterns
    10. 6.10. Associated Constants
    11. 6.11. Custom Allocators
  7. 7. Glossary
  8. 8. Syntax Index
  9. 9. Bibliography

Strings

Strings are an important concept for any programmer to master. Rust’s string handling system is a bit different from other languages, due to its systems focus. Any time you have a data structure of variable size, things can get tricky, and strings are a re-sizable data structure. That being said, Rust’s strings also work differently than in some other systems languages, such as C.

Let’s dig into the details. A ‘string’ is a sequence of Unicode scalar values encoded as a stream of UTF-8 bytes. All strings are guaranteed to be a valid encoding of UTF-8 sequences. Additionally, unlike some systems languages, strings are not NUL-terminated and can contain NUL bytes.

Rust has two main types of strings: &str and String. Let’s talk about &str first. These are called ‘string slices’. A string slice has a fixed size, and cannot be mutated. It is a reference to a sequence of UTF-8 bytes.

let greeting = "Hello there."; // greeting: &'static strRun

"Hello there." is a string literal and its type is &'static str. A string literal is a string slice that is statically allocated, meaning that it’s saved inside our compiled program, and exists for the entire duration it runs. The greeting binding is a reference to this statically allocated string. Any function expecting a string slice will also accept a string literal.

String literals can span multiple lines. There are two forms. The first will include the newline and the leading spaces:

let s = "foo
    bar";

assert_eq!("foo\n    bar", s);Run

The second, with a \, trims the spaces and the newline:

let s = "foo\
    bar";

assert_eq!("foobar", s);Run

Note that you normally cannot access a str directly, but only through a &str reference. This is because str is an unsized type which requires additional runtime information to be usable. For more information see the chapter on unsized types.

Rust has more than only &strs though. A String is a heap-allocated string. This string is growable, and is also guaranteed to be UTF-8. Strings are commonly created by converting from a string slice using the to_string method.

let mut s = "Hello".to_string(); // mut s: String
println!("{}", s);

s.push_str(", world.");
println!("{}", s);Run

Strings will coerce into &str with an &:

fn takes_slice(slice: &str) {
    println!("Got: {}", slice);
}

fn main() {
    let s = "Hello".to_string();
    takes_slice(&s);
}Run

This coercion does not happen for functions that accept one of &str’s traits instead of &str. For example, TcpStream::connect has a parameter of type ToSocketAddrs. A &str is okay but a String must be explicitly converted using &*.

use std::net::TcpStream;

TcpStream::connect("192.168.0.1:3000"); // &str parameter

let addr_string = "192.168.0.1:3000".to_string();
TcpStream::connect(&*addr_string); // convert addr_string to &strRun

Viewing a String as a &str is cheap, but converting the &str to a String involves allocating memory. No reason to do that unless you have to!

Indexing

Because strings are valid UTF-8, they do not support indexing:

let s = "hello";

println!("The first letter of s is {}", s[0]); // ERROR!!!Run

Usually, access to a vector with [] is very fast. But, because each character in a UTF-8 encoded string can be multiple bytes, you have to walk over the string to find the nᵗʰ letter of a string. This is a significantly more expensive operation, and we don’t want to be misleading. Furthermore, ‘letter’ isn’t something defined in Unicode, exactly. We can choose to look at a string as individual bytes, or as codepoints:

let hachiko = "忠犬ハチ公";

for b in hachiko.as_bytes() {
    print!("{}, ", b);
}

println!("");

for c in hachiko.chars() {
    print!("{}, ", c);
}

println!("");Run

This prints:

229, 191, 160, 231, 138, 172, 227, 131, 143, 227, 131, 129, 229, 133, 172,
忠, 犬, ハ, チ, 公,

As you can see, there are more bytes than chars.

You can get something similar to an index like this:

let dog = hachiko.chars().nth(1); // kinda like hachiko[1]Run

This emphasizes that we have to walk from the beginning of the list of chars.

Slicing

You can get a slice of a string with slicing syntax:

let dog = "hachiko";
let hachi = &dog[0..5];Run

But note that these are byte offsets, not character offsets. So this will fail at runtime:

let dog = "忠犬ハチ公";
let hachi = &dog[0..2];Run

with this error:

thread 'main' panicked at 'index 0 and/or 2 in `忠犬ハチ公` do not lie on
character boundary'

Concatenation

If you have a String, you can concatenate a &str to the end of it:

let hello = "Hello ".to_string();
let world = "world!";

let hello_world = hello + world;Run

But if you have two Strings, you need an &:

let hello = "Hello ".to_string();
let world = "world!".to_string();

let hello_world = hello + &world;Run

This is because &String can automatically coerce to a &str. This is a feature called ‘Deref coercions’.