Strings

String and &str

std

str (String Slice) is a primitive type representing a view into a sequence of UTF-8 encoded bytes:

  • It is immutable.
  • Since str's size is unknown at compile time, one can only handle it behind a pointer. It is most often seen in its borrowed form as &str.
  • &str is a fat pointer containing a pointer to the string data (which can reside on the heap, stack, or in static memory) and its length.
  • &str is used when you need a view of existing string data without taking ownership. This is common for function arguments, where you don't need to own the string.

String literals (e.g., "hello") are of type &'static str, meaning they exist for the entire lifetime of the program.

String is a growable, mutable, owned string allocated on the heap:

  • It is similar to a Vec<u8> that is guaranteed to hold valid UTF-8.
  • String owns its data, meaning when a String goes out of scope, the memory it occupies on the heap is automatically deallocated.
  • Use String when you need to modify a string, own string data (e.g., to pass it to another thread or store it in a struct that owns it), or create a new string at runtime.

The relationship between String and &str is similar to that between Vec<T> and &[T] (a vector and a slice, respectively). String owns the underlying buffer, while &str is a reference to a portion of that buffer or some other string data.

#![allow(unused_assignments)]
//! This example demonstrates common string types in Rust.

// `String` is a growable, heap-allocated data structure that is
// Unicode, not ASCII.
fn string() {
    // Create an empty string:
    let mut s1 = String::new();

    // Create a `String` from a string literal via the `From` trait.
    s1 = String::from("hello");

    // `String` can be mutated:
    s1.push(',');
    // `push_str` appends a string slice to a `String`.
    s1.push_str(" world!");
    // `clear` empties the String, making it equal to "".
    s1.clear();

    // Alternative initialization from string literals:
    // `to_string` is available on any type that implements
    // the `Display` trait.
    s1 = "contents".to_string();

    println!("String: {s1}");
}

// The `str` type, also called a 'string slice', is the most primitive string
// type. It is usually seen in its borrowed form, `&str`.
fn string_slices() {
    let my_string = String::from("Rust is awesome!");

    // Create a string slice pointing to the entire `String`:
    let slice1 = &my_string[..]; // Using `Index<RangeFull, Output = str>`.
    println!("Slice of an entire `String`: '{slice1}'");

    // The following works as well:
    let _slice2 = &*my_string; // Using `Deref<Target = str>`.
    let _slice3: &str = &my_string; // Type ascription.

    // Create a string slice pointing to a part of the `String`:
    let slice4: &str = &my_string[0..4]; // "Rust"
    println!("Slice of part of a `String`: '{slice4}'");
    // Caution: If we were to try to slice only part of a unicode
    // character's bytes, Rust would panic at runtime.
    // String slices must always be valid UTF-8!

    // Convert a `String` to `&str` explicitly using `as_str()`.
    fn print_slice(s: &str) {
        println!("Printing a slice: '{s}'");
    }
    print_slice(my_string.as_str());

    // You can also convert bytes (in a vector or an array) into a string slice
    // via `std::str::from_utf8`, _if the bytes are valid UTF-8_.
}

/// String literals are immutable, fixed-size sequences of characters that are
/// known at compile time. They are typically embedded directly into your
/// program's binary.
fn string_literals() {
    // String literals are string slices of type `&'static str`.
    let literal: &'static str = "Hello, world!";
    println!("String literal: '{literal}'");

    // You can't directly modify a string literal.
    // ERROR: literal.push_str("!");

    // String literals can be used to create a `String`.
    let string_from_literal: String = literal.to_string();
    println!("String from literal: '{string_from_literal}'");
}

fn common_operations() {
    let s1 = String::from("hello");
    let s2 = String::from(" Rust");

    // Concatenation:
    let _s = s1 + &s2;
    // Note: s1 is moved and can no longer be used afterwards.
    // The `+` operator takes ownership of the first string and appends
    // a string slice to it.

    // Formatting:
    let s1 = String::from("hello");
    let _s = format!("{s1}{s2}");

    // Iteration:
    // Character by character:
    for c in "Зд".chars() {
        println!("{c}");
    }
    // Byte by byte:
    for b in "Зд".bytes() {
        println!("{b}");
    }
}

fn main() {
    string();
    string_slices();
    string_literals();
    common_operations();
}

Print and Format Strings

std

print! and its siblings (like println! and format!) take a format string as its primary argument and prints it to the standard output (the terminal, usually). Format strings are string literals that can contain placeholders, indicated by curly braces {}. These placeholders tell print! where to insert values you provide as additional arguments or variables. Use {:?} placeholders for std::fmt::Debug↗ output or {:#?} for pretty printing.

You may also use dbg! for debug output. dbg! returns ownership of the expression's value, so it can be inserted almost anywhere.

use std::io::Write;

/// Example of using the `print` and `println!` macros:
fn print() {
    // The first argument of `print` must be a string literal.
    print!("This prints the string literal. ");
    std::io::stdout().flush().unwrap(); // Emit message immediately, if stdout is line-buffered.

    println!("This prints a newline after the string.");

    // `print` and related macros accept `{...}` as placeholders.
    // Placeholders can refer to an argument or variable.
    // The empty `{}` means "the next argument".
    let x = 5;
    let y = 10;
    println!("x = {x} and y + 2 = {}.", y + 2);
}

/// Variants with `eprint`, `format` and `write`:
fn variants() {
    // `eprint` and `eprintln` write to `io::stderr` instead of `io::stdout`:
    eprintln!("Error: Could not complete task {}.", 42);

    // `format` creates a `String`:
    let info = format!("{0}-{1}", "First", "Second");
    println!("{info}");

    // `write` and `writeln` write formatted data into a writer.
    // The writer may be any value with a `write_fmt` method,
    // and usually implements the `fmt::Write` or `io::Write` traits.
    // Here this is a simple buffer String:
    use std::fmt::Write;
    let mut output = String::new();
    let z = "Rust";
    write!(&mut output, "Hello {z}!")
        .expect("Error occurred while trying to write to a String.");
}

/// `print` and friends call `format_args` under the covers.
/// They support a large number of placeholder formats:
fn format() {
    let name = "Alice";
    let age = 30;

    // Substitute variables into placeholders:
    println!("Hello, my name is {name} and I am {age} years old.");
    // Output: Hello, my name is Alice and I am 30 years old.

    // Use named arguments for clarity:
    print!(
        "{subject} is a wonderful city to live in. ",
        subject = String::from("Seattle")
    );
    // Output: Seattle is a wonderful city to live in.

    // Use positional arguments (less common but can be useful).
    let info = format!("{0} {1} {0}", "+", "-");
    println!("{info}"); // Output: + - +

    // You can format numbers with specific precision and alignment:
    let pi = std::f32::consts::PI;
    println!("Pi to two decimal places: {pi:.2}");
    // Output: Pi to two decimal places: 3.14.

    let number = 123;
    println!("Right aligned with width 10: {number:>10}");
    // Output: Right aligned with width 10:        123.

    // Print binary, hexadecimal...
    println!("Binary representation: {number:b}");
    // Output: Binary representation: 1111011.

    println!("Hexadecimal representation: {:x}", 255);
    // Output: Hexadecimal representation: ff.

    println!("Uppercase Hexadecimal: {:X}", 255);
    // Output: Uppercase Hexadecimal: FF.

    // If no format letter is specified (as in `{}` or `{:6}`),
    // `format_args` uses the `Display` trait.

    // `:?` and friends use the `Debug` trait.

    /// An example struct that implements the `Debug` trait.
    #[allow(dead_code)]
    #[derive(Debug)]
    struct Point {
        x: i32,
        y: i32,
    }
    let p = Point { x: 10, y: 20 };
    println!("Debug output of Point: {p:?}"); // `p` must implement `Debug`.
    // Output: Debug output of Point: Point { x: 10, y: 20 }

    // Pretty debug formatting (using `:#?`):
    println!("Pretty debug output:\n{p:#?}");
    // Output:
    // Pretty debug output:
    // Point {
    //     x: 10,
    //     y: 20,
    // }

    // Prints and returns the value of a given expression for quick and dirty
    // debugging. Note that the `dbg!` macro works exactly the same in
    // release builds.
    let _q = dbg!(Point { x: 5, y: 5 });
}

fn main() {
    print();
    variants();
    format();
}

Concatenate Strings

See the String Concatenation chapter and the Concatenation Benchmark↗.

String Manipulation

See:

  • Regex (Regular Expressions).
  • Parsing.
  • Text Processing.
  • String Parsing.

Related Data Structures

  • Slices.
  • Vectors.
  • COW.
  • Algorithms.
  • Encoding.
  • Internationalization.
  • Localization.
  • Search.
  • Rust Search Engines.
  • Template Engine.
  • Text Layout.
  • Unicode handling.
  • Value Formatting.
  • Working with Other Strings (CString, OsString).