Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
16: The Slice Type
Up until now, you've dealt with owners—things like String and Vec that hold onto their data for dear life. But in the real world, you rarely need to own the whole collection just to look at a piece of it. That's where slices come in.
Wait, so is a slice just a pointer?
Not exactly. A regular reference (like &i32) is just a pointer to a single spot in memory. A slice, however, is what we call a "fat pointer." It's actually two pieces of data bundled together: a pointer to the start of the sequence and the length of that sequence.
I like to think of it as a window. The slice doesn't own the data; it just tells Rust, "Start here, and look at the next X elements." This is why slices are so efficient—they don't copy the data they point to. They just point to a segment of an existing array or vector.
let numbers = [10, 20, 30, 40, 50];
// This is a slice of the array.
// It doesn't copy the numbers; it just references a window of them.
let slice: &[i32] = &numbers[1..4];
println!("{:?}", slice); // Output: [20, 30, 40]
Why am I seeing &str everywhere instead of String?
This is one of the most common points of confusion for people coming from other languages. A String is a heap-allocated buffer that you own. A &str (a string slice) is just a view into a string.
Whenever I'm writing a function that needs to read some text, I almost always use &str as the argument type. Why? Because it's more flexible. If your function takes &String, you can only pass it a String object. But if it takes &str, you can pass it a String, a string literal, or even a slice of another string.
fn announce(message: &str) {
println!("Announcement: {}", message);
}
let owned_string = String::from("System update");
let literal_string = "All clear";
// Both of these work because Rust "coerces" String to &str
announce(&owned_string);
announce(literal_string);
How do I actually grab a specific piece of a collection?
You use range syntax. The most common is [start..end], where start is inclusive and end is exclusive. If you want to include the end index, you use [start..=end].
I'll give you a practical example. Imagine you're parsing a raw log line where the first 10 characters are always a timestamp, and the rest is the message. You don't want to create a whole new String just to see the message; you just want a slice of the existing line.
let log_line = "2023-10-27 ERROR: Database connection failed";
let timestamp = &log_line[0..10];
let message = &log_line[11..]; // Leaving the end blank goes to the end of the string
println!("Time: {}, Msg: {}", timestamp, message);
One word of caution: Rust is strict about safety. If you try to slice outside the bounds of the collection (say, [0..100] on a 10-character string), the program will panic. If you aren't sure about the length, look into the .get() method, which returns an Option instead of crashing.
📋 Practical Task
Exercise: Log Level Extractor
You are building a log analyzer. You have a list of log entries, and each entry starts with a fixed-width status code in brackets, like [INFO] or [WARN] . Your goal is to extract just the status code (the text inside the brackets) without creating new String allocations.
Complete the following code by implementing the extract_level function using string slicing:
fn extract_level(log: &str) -> &str {
// Your code here: return a slice containing only the
// text between the brackets (index 1 to 4)
}
fn main() {
let log1 = "[INFO] System started";
let log2 = "[WARN] Low disk space";
let log3 = "[ERR ] Connection lost";
println!("Level 1: {}", extract_level(log1)); // Should print: INFO
println!("Level 2: {}", extract_level(log2)); // Should print: WARN
println!("Level 3: {}", extract_level(log3)); // Should print: ERR
}There are no comments for now.