| |

Rust 18 🦀 Slice Type: String Slices (&str) and Array Slices

A slice is a reference to a contiguous sequence of elements within a collection. It is not the collection itself; it is a view into it. Rust has two slice types that appear constantly in real code: the string slice &str, which is a view into a string, and the array slice &[T], which is a view into an array or a vector. Both are fat pointers: they carry a pointer to the first element and a length. They do not own the data; they borrow it. This is what makes them cheap to pass around and what makes them the idiomatic parameter type for functions that read sequences.

The slice type is the reason functions in Rust prefer &str over &String and &[T] over &Vec<T>. A &str accepts both string literals and &String through deref coercion. A &[T] accepts both arrays and vectors. The slice is more general than the owning type, so a function that takes a slice accepts more inputs without any loss of capability. This chapter covers the string slice, the array slice, the fat pointer representation, slicing syntax, the range forms, deref coercion, and the patterns that make slices the default parameter type.

Key point: A slice is a reference to a contiguous sequence of elements. &str is a view into a string; &[T] is a view into an array or vector. A slice is a fat pointer with a pointer and a length. Prefer &str over &String and &[T] over &Vec<T> in function signatures, because the slice types are more general. Slicing uses ranges: &s[0..5], &v[..3], &v[2..], &v[..].


Why slices exist

The borrowing problem. Passing an owned String to a function transfers ownership. Passing a &String borrows the whole string. Neither is the right choice for a function that reads part of a string. The &str slice is the right choice: it borrows a view into the string, whether it is the whole thing or a part.

The generality problem. A function that takes &String accepts only &String. A function that takes &str accepts &String, &str, and string literals. The slice type is more general, so the function is more reusable.

The array-and-vector problem. A function that takes &Vec<T> accepts only &Vec<T>. A function that takes &[T] accepts &Vec<T> and arrays. The slice type unifies the two.

The fat-pointer problem. A slice is a pointer and a length. The pointer says where the sequence starts, and the length says how many elements it contains. The two together are the minimum needed to represent a view into a contiguous sequence.

The safety problem. A slice cannot outlive the collection it borrows from. The borrow checker enforces this, so the slice cannot dangle. The length is always within the bounds of the collection, and the indexing is checked.


a. The string slice &str

A &str is a reference to a string slice. It is a view into a string, and it does not own the data.

let greeting: &str = "Hello, world!";

The "Hello, world!" is a string literal, which has the type &'static str. It is stored in the program’s read-only data and lives for the entire program. The &str is a reference to that data.

A &str can also be a view into a String:

let s = String::from("Hello, world!");
let slice: &str = &s;

The &s is a &String, and deref coercion converts it to &str. The slice borrows the same data as the String, and the String retains ownership.

A &str can be a view into part of a string:

let s = String::from("Hello, world!");
let hello: &str = &s[0..5];
let world: &str = &s[7..12];

The &s[0..5] is a slice of the first five characters. The &s[7..12] is a slice of the next five. Both are views into the same String, and the String retains ownership.


b. The array slice &[T]

An array slice is a reference to a contiguous sequence of elements of type T.

let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &numbers;

The &numbers is a &[i32; 5], and deref coercion converts it to &[i32]. The slice borrows the array, and the array retains ownership.

A slice of a vector:

let v = vec![1, 2, 3, 4, 5];
let slice: &[i32] = &v;

The &v is a &Vec<i32>, and deref coercion converts it to &[i32]. The slice borrows the vector’s buffer.

A slice of part of a collection:

let v = vec![1, 2, 3, 4, 5];
let first_two: &[i32] = &v[0..2];
let rest: &[i32] = &v[2..];

The &v[0..2] is a slice of the first two elements. The &v[2..] is a slice of the remaining elements.


c. The fat pointer representation

A slice is a fat pointer. It contains a pointer to the first element and a length.

┌──────────────────────────────────┐
│  &[T]                            │
│  ┌───────────┬───────────┐       │
│  │  pointer  │  length   │       │
│  └───────────┴───────────┘       │
│       │                          │
│       ▼                          │
│  ┌───┬───┬───┬───┬───┐           │
│  │ 1 │ 2 │ 3 │ 4 │ 5 │           │
│  └───┴───┴───┴───┴───┘           │
│    ▲                             │
│    └── the pointer points here   │
└──────────────────────────────────┘

The pointer is the address of the first element, and the length is the number of elements. The two words together are the slice. On a 64-bit system, a slice is 16 bytes: 8 for the pointer and 8 for the length.

The &str is the same: a pointer to the first byte and a length in bytes. It does not include the capacity, because a slice does not own the data and does not need to know how much space is allocated.

The memory layout of a slice is:

FieldSizePurpose
Pointer8 bytesAddress of the first element
Length8 bytesNumber of elements
Total16 bytes

The std::mem::size_of::<&[i32]>() returns 16 on a 64-bit system. The same is true for &str.


d. Slicing syntax

The slicing syntax uses ranges inside square brackets.

RangeMeaning
&s[0..5]Elements at indices 0 through 4
&s[..5]Elements from the start through index 4
&s[2..]Elements from index 2 to the end
&s[..]All elements
&s[2..=4]Elements at indices 2 through 4 (inclusive)
let s = String::from("Hello, world!");

let hello = &s[0..5];    // "Hello"
let comma = &s[5..6];    // ","
let world = &s[7..12];   // "world"
let all = &s[..];        // "Hello, world!"

The range is half-open by default: the start is inclusive, and the end is exclusive. The ..= syntax makes the end inclusive.

let v = vec![1, 2, 3, 4, 5];

let first = &v[0];           // 1 (indexing, not slicing)
let slice = &v[0..2];        // [1, 2]
let tail = &v[2..];          // [3, 4, 5]
let inclusive = &v[1..=3];   // [2, 3, 4]

Indexing with a single value returns a reference to the element. Slicing with a range returns a slice.

The range can be a variable:

let start = 1;
let end = 3;
let slice = &v[start..end];  // [2, 3]

The compiler checks the bounds at runtime. If the range is out of bounds or the start is greater than the end, the program panics. This is the same behavior as indexing.


e. Deref coercion

Deref coercion is the mechanism that converts a reference to an owning type into a reference to the borrowed type.

FromToTrigger
&String&strString implements Deref<Target = str>
&Vec<T>&[T]Vec implements Deref<Target = [T]>
&Box<T>&TBox implements Deref<Target = T>
&[T; N]&[T]Arrays coerce to slices
fn length(s: &str) -> usize {
    s.len()
}

let s = String::from("hello");
length(&s);       // &String coerces to &str
length("hello");  // &str literal

The function takes &str, and the call sites pass a &String and a &str. The first is coerced automatically. The second is already the right type.

fn sum(values: &[i32]) -> i32 {
    values.iter().sum()
}

let v = vec![1, 2, 3];
sum(&v);          // &Vec<i32> coerces to &[i32]

let arr = [1, 2, 3];
sum(&arr);        // &[i32; 3] coerces to &[i32]

The function takes &[i32], and the call sites pass a &Vec<i32> and a &[i32; 3]. Both are coerced automatically.

The deref coercion happens at the call site, not in the function. The function signature says &str or &[T], and the caller passes whatever coerces to it.


f. Slices in function signatures

The slice types are the idiomatic parameter types for functions that read sequences.

// Prefer this
fn length(s: &str) -> usize { s.len() }
fn sum(values: &[i32]) -> i32 { values.iter().sum() }

// Over this
fn length(s: &String) -> usize { s.len() }
fn sum(values: &Vec<i32>) -> i32 { values.iter().sum() }

The slice versions accept more inputs. A &String parameter accepts only &String; a &str parameter accepts &String, &str, and string literals. A &Vec<T> parameter accepts only &Vec<T>; a &[T] parameter accepts &Vec<T> and arrays.

A function that modifies the collection and needs to change its length cannot take a slice, because a slice cannot grow or shrink. It must take &mut Vec<T> or &mut String. But a function that modifies elements in place can take &mut [T]:

fn double_all(values: &mut [i32]) {
    for v in values.iter_mut() {
        *v *= 2;
    }
}

let mut v = vec![1, 2, 3];
double_all(&mut v);
println!("{:?}", v);  // [2, 4, 6]

The &mut [i32] allows modifying the elements without changing the length. The caller can pass a &mut Vec<i32> or a &mut [i32; N], and deref coercion converts it.


Complete Example Session

// ============================================
// PART 1: STRING LITERAL
// ============================================
fn main() {
    let greeting: &str = "Hello, world!";
    println!("{}", greeting);
}
// ============================================
// PART 2: STRING SLICE FROM A STRING
// ============================================
fn main() {
    let s = String::from("Hello, world!");
    let hello = &s[0..5];
    let world = &s[7..12];
    println!("{} {}", hello, world);  // Hello world
}
// ============================================
// PART 3: ARRAY SLICE
// ============================================
fn main() {
    let numbers = [1, 2, 3, 4, 5];
    let slice: &[i32] = &numbers;
    println!("{:?}", slice);
}
// ============================================
// PART 4: SLICE FROM A VECTOR
// ============================================
fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let first_two = &v[0..2];
    let rest = &v[2..];
    println!("{:?} {:?}", first_two, rest);
}
// ============================================
// PART 5: FAT POINTER SIZE
// ============================================
fn main() {
    println!("{}", std::mem::size_of::<&[i32]>());  // 16
    println!("{}", std::mem::size_of::<&str>());    // 16
}
// ============================================
// PART 6: RANGE FORMS
// ============================================
fn main() {
    let v = vec![1, 2, 3, 4, 5];
    println!("{:?}", &v[..3]);     // [1, 2, 3]
    println!("{:?}", &v[2..]);     // [3, 4, 5]
    println!("{:?}", &v[1..=3]);   // [2, 3, 4]
    println!("{:?}", &v[..]);      // [1, 2, 3, 4, 5]
}
// ============================================
// PART 7: DEREF COERCION
// ============================================
fn length(s: &str) -> usize {
    s.len()
}

fn main() {
    let s = String::from("hello");
    println!("{}", length(&s));       // 5
    println!("{}", length("hello"));  // 5
}
// ============================================
// PART 8: SLICE PARAMETER FOR VECTORS AND ARRAYS
// ============================================
fn sum(values: &[i32]) -> i32 {
    values.iter().sum()
}

fn main() {
    let v = vec![1, 2, 3];
    let arr = [4, 5, 6];
    println!("{}", sum(&v));    // 6
    println!("{}", sum(&arr));  // 15
}
// ============================================
// PART 9: MUTABLE SLICE
// ============================================
fn double_all(values: &mut [i32]) {
    for v in values.iter_mut() {
        *v *= 2;
    }
}

fn main() {
    let mut v = vec![1, 2, 3];
    double_all(&mut v);
    println!("{:?}", v);  // [2, 4, 6]
}
// ============================================
// PART 10: SLICING WITH VARIABLES
// ============================================
fn main() {
    let s = String::from("Hello, world!");
    let start = 7;
    let end = 12;
    let slice = &s[start..end];
    println!("{}", slice);  // world
}

These ten parts cover the string literal, a string slice from a String, an array slice, a slice from a vector, the fat pointer size, the range forms, deref coercion, a slice parameter for vectors and arrays, a mutable slice, and slicing with variables.


Quick Reference

Slice Types

TypeView intoElements
&strString or literalBytes (UTF-8)
&[T]Array or vectorElements of type T
&mut [T]Array or vectorMutable elements

Range Forms

FormMeaning
&s[0..5]Indices 0 to 4
&s[..5]Start to index 4
&s[2..]Index 2 to end
&s[..]All elements
&s[2..=4]Indices 2 to 4 inclusive

Fat Pointer

FieldSize
Pointer8 bytes
Length8 bytes
Total16 bytes (64-bit)

Deref Coercion

FromTo
&String&str
&Vec<T>&[T]
&Box<T>&T
&[T; N]&[T]

Idiomatic Parameters

Instead ofUse
&String&str
&Vec<T>&[T]
&mut Vec<T> (when not growing)&mut [T]

Slice Methods

MethodPurpose
len()Number of elements
is_empty()Whether the slice is empty
first()First element
last()Last element
iter()Iterator
iter_mut()Mutable iterator
contains(&x)Membership
starts_with(&x)Prefix check
split_at(n)Split into two slices

Best Practices

✅ Do This:

// Use &str for string parameters
fn length(s: &str) -> usize { s.len() }

// Use &[T] for slice parameters
fn sum(values: &[i32]) -> i32 { values.iter().sum() }

// Use &mut [T] for in-place modification
fn double_all(values: &mut [i32]) { }

// Use the range forms
let hello = &s[0..5];
let tail = &v[2..];

// Rely on deref coercion
length(&string);
sum(&vec);

❌ Don’t Do This:

// Use &String when &str suffices
fn length(s: &String) -> usize { s.len() }  // ❌

// Use &Vec<T> when &[T] suffices
fn sum(values: &Vec<i32>) -> i32 { }  // ❌

// Index out of bounds
let x = &v[10];  // ❌ panics

// Slice a multi-byte character
let s = String::from("héllo");
let c = &s[0..2];  // ❌ may panic on UTF-8 boundary

Common Pitfalls

PitfallWhy It HappensFix
Panic on out-of-boundsRange exceeds lengthCheck len() first
UTF-8 boundary panicSlicing in the middle of a characterUse char_indices or chars
Dangling sliceSlice outlives the collectionThe borrow checker rejects this
&String instead of &strLess generalUse &str
&Vec<T> instead of &[T]Less generalUse &[T]
Slice used after mutationCollection modified while borrowedThe borrow checker rejects this

Real-World Examples

1. String Slice

let hello = &s[0..5];

2. Array Slice

let slice: &[i32] = &[1, 2, 3];

3. Vector Slice

let slice = &v[..3];

4. Slice Parameter

fn sum(values: &[i32]) -> i32 { values.iter().sum() }

5. Mutable Slice

fn double(values: &mut [i32]) { }

6. First and Last

let first = slice.first();
let last = slice.last();

7. Split At

let (left, right) = slice.split_at(3);

8. Starts With

if slice.starts_with(&[1, 2]) { }

9. String Prefix

let prefix = &s[..5];

10. Iterate a Slice

for item in slice.iter() { }

Visual

Fat Pointer

┌──────────────────────────────────────────────────────────────┐
│  &[T]                                                        │
│  ┌───────────────┬───────────────┐                           │
│  │   pointer     │    length     │                           │
│  └───────┬───────┴───────────────┘                           │
│          │                                                   │
│          ▼                                                   │
│  ┌────┬────┬────┬────┬────┐                                  │
│  │ 1  │ 2  │ 3  │ 4  │ 5  │                                  │
│  └────┴────┴────┴────┴────┘                                  │
│                                                              │
│  Pointer to the first element, length of the slice.          │
└──────────────────────────────────────────────────────────────┘

Slicing a String

┌──────────────────────────────────────────────────────────────┐
│  s: "Hello, world!"                                          │
│     0123456789...                                            │
│                                                              │
│  &s[0..5]  →  "Hello"                                        │
│  &s[7..12] →  "world"                                        │
│  &s[..]    →  "Hello, world!"                                │
│  &s[7..]   →  "world!"                                       │
│  &s[..5]   →  "Hello"                                        │
└──────────────────────────────────────────────────────────────┘

Deref Coercion

┌──────────────────────────────────────────────────────────────┐
│  fn length(s: &str) -> usize { s.len() }                     │
│                                                              │
│  let s = String::from("hello");                              │
│  length(&s)     →  &String coerces to &str                   │
│  length("hello") →  &str literal                             │
│                                                              │
│  fn sum(values: &[i32]) -> i32 { }                           │
│                                                              │
│  let v = vec![1, 2, 3];                                      │
│  sum(&v)        →  &Vec<i32> coerces to &[i32]               │
│                                                              │
│  let arr = [1, 2, 3];                                        │
│  sum(&arr)      →  &[i32; 3] coerces to &[i32]               │
└──────────────────────────────────────────────────────────────┘

Range Forms

┌──────────────────────────────────────────────────────────────┐
│  v = [1, 2, 3, 4, 5]                                         │
│                                                              │
│  &v[1..3]   →  [2, 3]     indices 1 and 2                    │
│  &v[..3]    →  [1, 2, 3]  start through index 2              │
│  &v[2..]    →  [3, 4, 5]  index 2 to end                     │
│  &v[..]     →  all        the whole slice                    │
│  &v[1..=3]  →  [2, 3, 4]  indices 1 through 3                │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
String slice&str
Array slice&[T]
Mutable slice&mut [T]
Fat pointerPointer + length (16 bytes on 64-bit)
Range forms[a..b], [..b], [a..], [..], [a..=b]
Deref coercion&String → &str, &Vec<T> → &[T]
Idiomatic string param&str
Idiomatic slice param&[T]
In-place modification&mut [T]
Bounds checkRuntime panic on out-of-bounds
UTF-8 safetySlicing must be on character boundaries

Key takeaways:

  • A slice is a reference to a contiguous sequence of elements. It is a view into a collection, not the collection itself. It does not own the data.
  • &str is a view into a string, and &[T] is a view into an array or vector. Both are fat pointers: a pointer and a length. The total size is 16 bytes on a 64-bit system.
  • Prefer &str over &String and &[T] over &Vec<T>. The slice types are more general, so the function accepts more inputs through deref coercion.
  • Slicing uses ranges. &s[0..5] is indices 0 through 4. &s[..5] is the start through 4. &s[2..] is index 2 to the end. &s[..] is everything. &s[2..=4] is inclusive.
  • Deref coercion converts owning references to slice references. &String coerces to &str, &Vec<T> to &[T], and &[T; N] to &[T]. The conversion happens at the call site.
  • A slice cannot outlive the collection it borrows from. The borrow checker enforces this, so a slice cannot dangle.
  • Slicing a string must respect UTF-8 boundaries. Slicing in the middle of a multi-byte character panics. Use char_indices or chars to navigate character boundaries.

Remember: Slices are the idiomatic way to pass sequences to functions in Rust. The &str and &[T] types are more general than &String and &Vec<T>, and deref coercion makes the call sites convenient. A slice is a fat pointer with a pointer and a length, so it is cheap to pass and it does not own the data. The borrow checker ensures that the slice does not outlive the collection, and the runtime checks that the indices are within bounds. When you write a function that reads a sequence, the parameter should be a slice. When you write a function that modifies elements in place without changing the length, the parameter should be a mutable slice. This is the pattern that makes Rust functions reusable and efficient.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!