Rust: Rust Slices: Efficient Data Views and String Slices

Last updated: 2026-08-26

A slice is a special type of reference in Rust—it does not point to the entire data set, but rather to a contiguous segment of it.

Slicing allows you to safely "extract" a portion of the data for processing without having to copy it.


1. What You'll Learn



2. Conceptual Diagrams

100%
flowchart LR
    subgraph "Heap memory"
        HEAP["'H','e','l','l','o','','R','u','s','t','!'"]
    end
    subgraph "String s"
        S_PTR["ptr"] --> HEAP
        S_LEN["len: 12"]
        S_CAP["cap: 12"]
    end
    subgraph "&str slice"
        SL_PTR["ptr"] -->|"Pointing Forward5Byte"| HEAP
        SL_LEN["len: 5"]
    end


3. The Story of a Data Analyst

(1) Problem: Extracting information from the logs is too slow

Maria is a data analyst at an e-commerce company. She needs to extract IP addresses from a massive volume of server logs:

"I need a way to extract data that allows me to 'just look, not copy'—just like running my finger along a line of text in a newspaper without having to write it down."

(2) Solutions for Rust Slices

RUST
fn main() {
    let log_line = "192.168.1.1 - - [01/Jul/2026:12:00:00] \"GET /index.html\"";

    // Slices -- do not copy, they just "point to" a portion of the original string
    let ip = &log_line[0..13];           // "192.168.1.1"
    let date = &log_line[20..38];        // "01/Jul/2026:12:00:00"
    let path = &log_line[50..62];        // "/index.html"

    println!("IP: {}", ip);
    println!("Date: {}", date);
    println!("Path: {}", path);

    // Key Points: No strings were copied! All slices point to different areas of log_line
    println!("The original logs are still available: {}", log_line);
}

A slice is like a "window" that points to a different view of the original data. There is no copying, no allocation—zero overhead. This is crucial when working with large-scale data.



4. The Principle of Slicing

(1) Memory Model

RUST
let s = String::from("Hello, Rust!");
let slice = &s[0..5];  // "Hello"
100%
graph TB
    subgraph "String s"
        S_ptr[ptr ──→ H e l l o ,   R u s t !]
        S_len[len: 12]
        S_cap[cap: 12]
    end
    subgraph "&str slice"
        SL_ptr[ptr ──→ H e l l o]
        SL_len[len: 5]
    end
    S_ptr -.-> heap[Load the data]
    SL_ptr -.-> heap
Feature Entire String &s[0..5] slice
Memory ptr + len + cap (3 bytes) ptr + len (2 bytes)
Ownership Owned Borrowed (cited)
Whether to copy data Do not copy
Access Range Entire string "Hello" (5 bytes)

(2) Scope Syntax

Syntax Meaning Example
[0..5] 0 to 5 (excluding 5) "Hello"
[..5] From the beginning to 5 "Hello"
[5..] From 5 to the end ", Rust!"
[..] Entire string "Hello, Rust!"

(3) Quick Reference for Slice Types

Slice Type Syntax Size Description
String slice &str 16 bytes ptr + len (fat pointer)
Array Slicing &[T] 16 bytes ptr + len (fat pointer)
Array reference &[T; N] 8 bytes ptr only (length known at compile time)
Variable slice &mut [T] 16 bytes ptr + len (modifiable elements)


5. Slicing Examples

▶ Example 1: String Slicing (Difficulty ⭐)

Output:

TEXT 📖 Display only
hello: '<hello>'
rust: '<rust>'
world: '<world>'
full: '<full>'
Literal Slicing: '<first_word>'
RUST
// ============================================
// Basic Usage of String Slicing
// ============================================

fn main() {
    let s = String::from("Hello, Rust World!");

    // Various Slicing Methods
    let hello = &s[..5];           // "Hello"
    let rust = &s[7..11];          // "Rust"
    let world = &s[12..];          // "World!"
    let full = &s[..];             // All

    println!("hello: '{}'", hello);
    println!("rust: '{}'", rust);
    println!("world: '{}'", world);
    println!("full: '{}'", full);

    // String literals are, by their very nature, &str
    let literal: &str = "Create a slice directly";
    let first_word = &literal[..2];
    println!("Literal Slicing: '{}'", first_word);
}

Output:

TEXT 📖 Display only
Rust Part: <rust>
Byte Index <i>: Character '<c>'
First 4 character slice: <safe_slice>







A string literal (such as "hello") is itself of type &str—a slice pointing to a binary file. It does not require the & prefix because it is already a reference.


▶ Example 2: String Slicing Boundary Pitfalls (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
Rust Part: <rust>
Byte Index <i>: Character '<c>'
First 4 character slice: <safe_slice>
RUST
// ============================================
// UTF-8 Boundary: Slices must be at character boundaries.
// ============================================

fn main() {
    let s = "RustProgramming";  // Number of bytes: R(1) u(1) s(1) t(1) Bian(3) Cheng(3) = 10 bytes

    // Security Slices
    let rust = &s[..4];         // "Rust" (first 4 bytes are exactly ASCII)
    println!("Rust Part: {}", rust);

    // The following are examples of errors (uncomment to see panic!)
    // let bad = &s[0..5];       // ❌ 5 is between 4 and 6, landing in the middle of "Bian"'s first byte

    // A Safe Approach: use chars() and char_indices() to iterate
    for (i, c) in s.char_indices() {
        println!("Byte Index {}: Character '{}'", i, c);
    }

    // Use char_indices to find safe slice boundaries
    if let Some((pos, _)) = s.char_indices().nth(4) {
        let safe_slice = &s[..pos];
        println!("First 4 character slice: {}", safe_slice);
    }
}

Output:

TEXT 📖 Display only
Array Slicing: <slice_arr>
Vector Slicing(first 3): <slice_vec>
Modified original vector: [1, 2, 3, 4, 5]
&[i32] Occupancy: <std::mem::size_of::<&[i32]>()> Byte
&[i32;5] Occupancy: <std::mem::size_of::<&[i32; 5]>()> Byte








Slices are indexed by bytes, not by characters. If a slice boundary falls in the middle of a multibyte character, the program will panic and crash. Use .char_indices() to obtain a safe character-boundary index.


▶ Example 3: Array Slicing vs. Vector Slicing (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
Array Slicing: <slice_arr>
Vector Slicing(first 3): <slice_vec>
Modified original vector: [1, 2, 3, 4, 5]
&[i32] Occupancy: <std::mem::size_of::<&[i32]>()> Byte
&[i32;5] Occupancy: <std::mem::size_of::<&[i32; 5]>()> Byte
RUST
// ============================================
// Slicing Arrays and Vectors
// ============================================

fn main() {
    // Array Slicing
    let arr = [1, 2, 3, 4, 5];
    let slice_arr = &arr[1..4];      // [2, 3, 4]
    println!("Array Slicing: {:?}", slice_arr);

    // Vector Slicing
    let vec = vec![10, 20, 30, 40, 50];
    let slice_vec = &vec[..3];       // [10, 20, 30]
    println!("Vector Slicing(first 3): {:?}", slice_vec);

    // Edit Slice Content (requires &mut)
    let mut numbers = vec![1, 2, 3, 4, 5];
    let mut_slice = &mut numbers[1..4];  // [2, 3, 4]
    mut_slice[0] = 99;                   // Modifying a slice affects the original vector.
    println!("Modified original vector: {:?}", numbers);

    // Slice Type Size
    println!("&[i32] Occupancy: {} Byte", std::mem::size_of::<&[i32]>());
    println!("&[i32;5] Occupancy: {} Byte", std::mem::size_of::<&[i32; 5]>());
}

Output:

TEXT 📖 Display only
The first word in the literal string: '<result1>'
String The First Word: '<result2>'
Array Slicing and: <sum>







A slice type (&[i32]) occupies 16 bytes (8 bytes for the pointer + 8 bytes for the length), while a regular reference (&[i32;5]) occupies only 8 bytes (just the pointer). This is what is known as a "fat pointer"—a slice is a pointer that includes length information.


▶ Example 4: Slices as Function Arguments (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
The first word in the literal string: '<result1>'
String The First Word: '<result2>'
Array Slicing and: <sum>
RUST
// ============================================
// &str As a function argument -- the most flexible approach
// ============================================

// ✅ Best Practices: Receive &str -- regardless of whether the input is &str or &String
fn first_word(s: &str) -> &str {
    for (i, &b) in s.as_bytes().iter().enumerate() {
        if b == b' ' {
            return &s[..i];
        }
    }
    &s[..]  // No spaces, return the entire string
}

fn main() {
    // Incoming &str Literal
    let result1 = first_word("hello world");
    println!("The first word in the literal string: '{}'", result1);

    // Incoming &String (automatically converts to &str)
    let s = String::from("Rust is awesome");
    let result2 = first_word(&s);  // &String Automatically convert to &str
    println!("String The First Word: '{}'", result2);

    // Passing an array slice
    let arr = [1, 2, 3, 4, 5];
    let sum: i32 = sum_slice(&arr[..]);  // Use [..] to convert array to slice
    println!("Array Slicing and: {}", sum);
}

fn sum_slice(slice: &[i32]) -> i32 {
    let mut total = 0;
    for x in slice {
        total += *x;
    }
    total
}

Output:

TEXT 📖 Display only
The first word in the literal string: 'hello'
String The First Word: 'Rust'
Array Slicing and: 15

Best Practice: When a function parameter accepts a string, use &str instead of &String. &String is automatically converted to &str (via a Deref cast), so the &str parameter is more versatile—it accepts both literals and String references.


▶ Example 5: Comprehensive Exercise—Log Parser (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
=== Log Analysis (Zero-Copy) ===
IP: <ip> Methods: <method> Path: <path>

=== Longest Path ===
Longest Path: '<find_longest(&paths)>'

=== Statistics on Numerical Slices ===
Grade Slices: [85, 92, 78, 95, 88, 70, 96]
Average: <avg>, Lowest: <min>, Highest: <max>
RUST
// ============================================
// Practical Guide to Slicing: Zero-Copy Log Parsing
// ============================================

fn parse_log_line(line: &str) -> (&str, &str, &str) {
    let ip_end = line.find(' ').unwrap_or(line.len());
    let ip = &line[..ip_end];

    let rest = &line[ip_end..].trim_start();
    let method_end = rest.find(' ').unwrap_or(rest.len());
    let method = &rest[..method_end];

    let path_part = &rest[method_end..].trim_start();
    let path_end = path_part.find(' ').unwrap_or(path_part.len());
    let path = &path_part[..path_end];

    (ip, method, path)
}

fn find_longest<'a>(strings: &[&'a str]) -> &'a str {
    strings.iter().max_by_key(|s| s.len()).unwrap_or(&"")
}

fn summarize(data: &[i32]) -> (f64, i32, i32) {
    if data.is_empty() {
        return (0.0, 0, 0);
    }
    let sum: i32 = data.iter().sum();
    let avg = sum as f64 / data.len() as f64;
    let min = *data.iter().min().unwrap();
    let max = *data.iter().max().unwrap();
    (avg, min, max)
}

fn main() {
    let logs = [
        "192.168.1.1 GET /index.html HTTP/1.1",
        "10.0.0.5 POST /api/login HTTP/1.1",
        "172.16.0.1 DELETE /api/user/42 HTTP/1.1",
    ];

    println!("=== Log Analysis (Zero-Copy) ===");
    for log in &logs {
        let (ip, method, path) = parse_log_line(log);
        println!("IP: {:<15} Methods: {:<6} Path: {}", ip, method, path);
    }

    println!("\n=== Longest Path ===");
    let paths: Vec<&str> = logs.iter().map(|l| {
        let (_, _, p) = parse_log_line(l);
        p
    }).collect();
    println!("Longest Path: '{}'", find_longest(&paths));

    println!("\n=== Statistics on Numerical Slices ===");
    let scores = [85, 92, 78, 95, 88, 70, 96];
    let (avg, min, max) = summarize(&scores);
    println!("Grade Slices: {:?}", scores);
    println!("Average: {:.1}, Lowest: {}, Highest: {}", avg, min, max);
}

Output:

TEXT 📖 Display only
=== Log Analysis (Zero-Copy) ===
IP: 192.168.1.1     Methods: GET    Path: /index.html
IP: 10.0.0.5        Methods: POST   Path: /api/login
IP: 172.16.0.1      Methods: DELETE Path: /api/user/42

=== Longest Path ===
Longest Path: '/api/user/42'

=== Statistics on Numerical Slices ===
Grade Slices: [85, 92, 78, 95, 88, 70, 96]
Average: 86.3, Lowest: 70, Highest: 96

During log parsing, the three &str values returned by parse_log_line all point to different regions of the original string—zero-copy, zero-allocation. find_longest accepts a slice of &[&str], making it extremely versatile. Statistical functions for numeric slices also only borrow data.


❓ FAQ

Q What is the difference between a slice and a reference?
A A slice is a fat pointer (pointer + length), while a regular reference is just a pointer. &str stores two pieces of information in memory: "where it points" and "how long it is," whereas &String stores only "where it points." A slice lets you know the boundaries of the data, preventing out-of-bounds access.
Q What is the relationship between a string slice &str and a String?
A &str can be thought of as a partial view of a String. The String owns the data, while &str borrows a portion of it. You can think of a String as an entire book, and &str as a page in that book.
Q Why does &s[0..5] cause a panic when used with Chinese characters?
A Because slices are indexed by bytes, and a single Chinese character in UTF-8 encoding takes up 3 bytes. &s[0..4] On an ASCII string, this would be exactly “hello,” but on the string “RustBianCheng,” it is “Rust” (4 bytes). &s[0..5] It falls on the middle byte of the character “Bian,” and the compiler cannot determine if this is a valid character, so it panics.
Q Should I use &str or &String as a parameter?
A Always use &str. This is because &String is automatically converted to &str (via dereferencing), but the reverse is not true. A parameter using &str can accept both string literals and String references—making it more versatile.
Q Does slicing vec![1,2,3] with &vec[..] create new data?
A No, it has zero overhead. Slicing simply creates a new pointer to the original vector’s data along with range information. It does not allocate memory or copy data.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Create a String, use [..] to slice the entire string, and verify that the type of the slice is &str.
  2. Difficulty ⭐⭐: Write a function fn last_word(s: &str) -> &str that returns the last word (separated by spaces). Call it on both a string literal and String.
  3. Difficulty ⭐⭐⭐: Create a string containing Chinese characters and emojis "Rust🦀BianCheng", use .char_indices() to find the safe slice boundary, and extract only the "Rust🦀" portion (Hint: 🦀 takes up 4 bytes).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏