Strings
Recipe | Crates |
---|---|
String and &str | |
Print and Format Strings | |
Concatenate Strings |
String
and &str
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 aString
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
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.
Related Topics
- Algorithms.
- Encoding.
- Internationalization.
- Localization.
- Search.
- Rust Search Engines.
- Template Engine.
- Text Layout.
- Unicode handling.
- Value Formatting.
- Working with Other Strings (CString, OsString).