Slices

Returning Slices

A function can return a slice reference (&[T], &mut [T], or &str) only when the backing elements outlive the function call—either because they were borrowed from an input parameter or because they reside in 'static memory.

Lifetime Rules When Returning a Slice

A slice does not own its elements; it is a 16-byte (ptr, len) view into an existing buffer. Consequently, the Rust borrow checker enforces three rules when returning a slice from a function:

  1. Returning a subslice of a single input parameter: Lifetime elision automatically ties the output slice's lifetime to the input parameter (fn trim_zeros(s: &[u8]) -> &[u8]).
  2. Returning a subslice when multiple reference parameters exist: You must annotate which input parameter the returned slice borrows from (fn select<'a>(a: &'a [i32], b: &[i32]) -> &'a [i32]).
  3. Never return a slice of a local array, Vec<T>, or String: Local variables are dropped when the function returns; return an owned Vec<T> or String instead.
Valid vs. Invalid Slice Return Sources:

  Caller Buffer (s: &[u8]) ──> fn strip_header(s: &[u8]) -> &[u8]  (VALID: borrows from caller)
  Static Memory ('static)  ──> fn default_name() -> &'static str   (VALID: lives forever)
  Local Stack/Heap Buffer  ──> fn bad() -> &[u8]                   (ERROR E0515: dropped at return)

Returning a Subslice of an Input Slice

fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
    match bytes.iter().position(|&b| b != 0) {
        Some(first_nonzero) => &bytes[first_nonzero..],
        None => &[],
    }
}

fn extract_between<'a>(text: &'a str, open: &str, close: &str) -> Option<&'a str> {
    let start = text.find(open)? + open.len();
    let rest = &text[start..];
    let end = rest.find(close)?;
    Some(&rest[..end])
}

fn main() {
    let raw = [0, 0, 0, 42, 0, 99];
    println!("Stripped: {:?}", strip_leading_zeros(&raw));

    let tag = String::from("<title>Rust 2024</title>");
    let open = String::from("<title>");
    let close = String::from("</title>");

    let content = extract_between(&tag, &open, &close);
    // `open` and `close` can be dropped immediately because `content` only borrows `tag`:
    drop(open);
    drop(close);

    println!("Extracted: {content:?}");
}
Stripped: [42, 0, 99]
Extracted: Some("Rust 2024")

Returning Mutable Subslices (&mut [T])

A function that accepts &mut [T] can return a narrowed &mut [T] or even a tuple of two disjoint mutable subslices ((&mut [T], &mut [T])) using .split_at_mut(mid):

fn payload_mut(packet: &mut [u8]) -> Option<&mut [u8]> {
    // Skip a 4-byte header and return a mutable view of the remaining payload
    packet.get_mut(4..)
}

fn main() {
    let mut frame = [0x01, 0x02, 0x00, 0x04, 10, 20, 30, 40];

    if let Some(body) = payload_mut(&mut frame) {
        body.reverse();
    }

    println!("Frame after reversing payload: {frame:?}");
}
Frame after reversing payload: [1, 2, 0, 4, 40, 30, 20, 10]

Why Returning a Slice to a Local Variable Fails (E0515)

Creating a Vec<T>, String, or stack array [T; N] inside a function and attempting to return &local[..] fails to compile because the backing memory is deallocated when the function exits:

// ❌ does not compile
fn build_greeting(name: &str) -> &str {
    let formatted = format!("Hello, {name}!");
    &formatted[..]
}

fn main() {}
error[E0515]: cannot return value referencing local variable `formatted`
 --> src/main.rs:4:5
  |
4 |     &formatted[..]
  |     ^---------^^^^
  |     ||
  |     |`formatted` is borrowed here
  |     returns a value referencing data owned by the current function

When a function constructs new sequence data rather than slicing an input buffer or returning a 'static literal, return the owned container (String, Vec<T>, or Box<[T]>) or Cow<'a, str> / Cow<'a, [T]>.