Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Error’d: Pickup Sticklers

      September 27, 2025

      From Prompt To Partner: Designing Your Custom AI Assistant

      September 27, 2025

      Microsoft unveils reimagined Marketplace for cloud solutions, AI apps, and more

      September 27, 2025

      Design Dialects: Breaking the Rules, Not the System

      September 27, 2025

      Building personal apps with open source and AI

      September 12, 2025

      What Can We Actually Do With corner-shape?

      September 12, 2025

      Craft, Clarity, and Care: The Story and Work of Mengchu Yao

      September 12, 2025

      Cailabs secures €57M to accelerate growth and industrial scale-up

      September 12, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025
      Recent

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025

      Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

      September 28, 2025

      The first browser with JavaScript landed 30 years ago

      September 27, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured
      Recent
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Rust Slices: Cutting Into References the Safe Way

    Rust Slices: Cutting Into References the Safe Way

    August 26, 2025

    What is a slice?

    It’s just a reference, but of course that is being over simplistic.

    Let’s delve deeper into what a slice type is.

    According to the official Rust documentation and cookbook,

    A slice can be defined as:

    • Slices let you reference a contiguous sequence of elements in a collection. A slice is a kind of reference, so it does not have ownership.

    One notable word to really understand what a slice is the word collection. So what is a collection?

    According to the standard library documentation:

    • Rust’s standard library includes a number of very useful data structures called collections. Most other data types represent one specific value, but collections can contain multiple values. Unlike the built-in array and tuple types, the data that these collections point to is stored on the heap, which means the amount of data does not need to be known at compile time and can grow or shrink as the program runs.

    In simple terms, it is a well-articulated list of most-used heap-based data structures. Collections are of different variants and groups but they all have one thing in common, and what is that?

    All collections store data on the heap, allowing them to be easily extensible and making compile-time size allocation easy as collection types only keep key information on the stack, like size.

    Now we have that out of the way, you can easily define a slice type. If you asked me, I would say it is simply a ranged reference to contiguous data. Because slices themselves are stored as a pointer and length (on the stack if local), the core data itself could be an array (on the stack), a Vec (on the heap), a string, or other contiguous memory.

    Why and when should we use a slice?

    Now we know what it is, let’s explore when to use and why we could need a slice.

    Slices are mostly about viewing data without taking ownership. You don’t always need to copy or move things around just to work with them. Instead, a slice gives you a window into the data, so you can read or mutate part of it while keeping the original owner intact.

    They shine in scenarios like:

    • Working with substrings or sub-arrays: Instead of creating a new string or array for a subset, you just borrow a slice of what you need.

    • Avoiding unnecessary copies: Since slices are lightweight references, you don’t pay extra performance costs for moving or cloning data.

    • Function parameters: Instead of writing separate functions for String and &str, or Vec<T> and &[T], you can just take a slice parameter and handle both cases with the same code.

    • Iteration with safety: Slices enforce boundaries — you won’t run past the edge of your data. This is both memory-safe and avoids panics when used with safe methods.

    So in short, use slices whenever you only need to look at or operate on part of a data structure without owning or duplicating it. They’re a way of being precise with memory and scope while still staying flexible in how your functions and APIs accept input.

    Slice of Strings.

    Since strings are a good example of contiguous memory , lets take it as an example of how to make a slice of a string and with this example you can learn the basics of working with slices ; we would of course then go into deeper categories like making slices of other collection groups.

    Let’s look at this example. In this example, we would create a string, take a slice of the string and pass it to a macro called println! to print it to the standard output.

    
    fn main() {
        let s1 : String = String::from("Hello world!");
        let s1slice = &s1[..5];
        println!("{s1slice}");
    } 

    In the above example you can see we can create a slice of a string by simply using the & ref and passing in a range, so in code a slice looks more like a reference with a range.

    Safety with Slices

    Indexing and panics:  Even though our previous example is totally ok , it is prone to runtime errors, why? If the given range is bigger than the size of the collection in respect, there would be a runtime error.

    In order to fix this, we can ensure we supply correct ranges or simply use helper methods that help avoid this entirely, as they would return an option type of Some(slice) or None when the range is too big, or any other possible issues arise.

    Here is an example of our previous function, but with a safer alternative using the .get method.

    
    fn main() {
        let s1 : String = String::from("Hello world!");
        let s1slice = s1.get(0..5).unwrap_or("Hello");
        // I use unwrap_or as a fall back, incases of a none.
        println!("{s1slice}");
    } 

    The way this works may differ slightly across collections, but the principle is the same: direct indexing can panic, so prefer safe methods when possible.

    • Note: Since String is essentially a Vec<u8> with UTF-8 rules, slicing at a non-UTF-8 boundary also causes a panic.

    Mutability Rules

    Yes, you can have mutable slices. As mentioned earlier, slices are references with a range, so you can also take a mutable slice (&mut [T] or &mut str). However, the borrow checker applies the usual rules: you can have either one mutable slice or many immutable slices at the same time, but not both. This ensures safety when modifying data through slices.

    Here is an example showing this:

    
    fn main() {
        let mut numbers = [1, 2, 3, 4, 5];
        let slice = &mut numbers[1..4]; // mutable slice of part of the array
    
        for n in slice.iter_mut() {
            *n *= 2;
        }
    
        println!("{:?}", numbers); // [1, 4, 6, 8, 5]
    }

    Lifetime & Ownership

    Even though slices reference owned data, they themselves do not own it. A slice is just a fat pointer (a reference plus a length). For example, &[u8] means “a reference to a sequence of u8s.”

    The underlying elements are not duplicated — the slice just points to them.

    This means:

    • If you slice a vector of values, you get a slice of those values.

    • If you slice a vector of references, you get a slice of references.

    Let’s look at some examples that expose this:

    
    fn main() {
        let v1: Vec<i32> = vec![1, 2, 3, 4];
        let v2: Vec<&i32> = vec![&1, &2, &3, &4];
    
        let slicev1: Option<&[i32]> = v1.get(1..3);
        let slicev2: Option<&[&i32]> = v2.get(1..3);
    
        println!("{:?}", slicev1); // Some([2, 3])
        println!("{:?}", slicev2); // Some([&2, &3])
    }

    Notice the type signatures:

    • slicev1 has type Option<&[i32]>, meaning a slice of i32s.

    • slicev2 has type Option<&[&i32]>, meaning a slice of references to i32s.

    This shows that slices preserve the nature of what they point to — values stay values, and references stay references.

    So the core ownership rules for references are still in effect, and just like ownership rules lifetime rules are stay the same — slices cannot live longer than their owners the compiler enforces this.

    Borrow Checker

    Just like other points related to references, you can’t bend the borrow checking rules around slices either.

    The same restrictions apply: you can have either many immutable borrows or one mutable borrow at a time, but never both. Attempting to break this rule results in a compiler error, not a runtime panic.

    For example, if you try to mutate a slice while still holding an immutable reference to it, the borrow checker will reject it at compile time.

    Likewise, if you attempt to create two overlapping mutable slices of the same data, Rust prevents it.

    These rules ensure that slices are always safe to use without data races or undefined behavior.

    Safe Iteration

    Instead of direct indexing of slices, you might want to use iterator methods such as .iter() and .iter_mut() when you want to read or modify elements in sequence. Iterators avoid the risk of indexing mistakes and make the code more expressive. In cases where you need ownership of the elements, .into_iter() is also available.

    Additionally, when working with ranges, the .get() method is a good choice because it returns an Option and avoids panics if the range is invalid. This is especially useful for dynamic scenarios where you cannot guarantee the bounds at compile time.

    Limitations

    As seen so far, slices and their usages are tied to contiguous memory data structures such as arrays, vectors, and strings. They provide a safe view into a block of sequential elements. However, not all collections in Rust are contiguous — for example, HashMap, HashSet, and LinkedList are not laid out sequentially in memory.

    For these collections, you cannot take a slice directly. Instead, you rely on their own iterator methods or conversion functions to access data safely. If you require slice-like functionality, you would often need to first collect the elements into a contiguous container like a Vec.

    Next Steps

    In the next section, we will show some examples of getting slices out of popular collections, highlighting how they behave with vectors, arrays, and strings.

    The Four Groups of Collections and How to Take Slices of Them

    Rust’s collections can be grouped into four major categories. Out of these, only the contiguous-memory collections support slicing directly. The others provide access through iteration or conversion. Let’s walk through them one by one.

    1. Sequence Collections

    These are contiguous-memory collections such as arrays, vectors, and strings. They support slicing directly.

    
    fn main() {
        // Array slice
        let arr = [10, 20, 30, 40, 50];
        let arr_slice: &[i32] = &arr[1..4];
        println!("{:?}", arr_slice); // [20, 30, 40]
    
        // Vector slice
        let v = vec![1, 2, 3, 4, 5];
        let v_slice: &[i32] = &v[0..3];
        println!("{:?}", v_slice); // [1, 2, 3]
    
        // String slice
        let s = String::from("Hello, world!");
        let s_slice: &str = &s[0..5];
        println!("{}", s_slice); // "Hello"
    }

    2. Map Collections

    Examples: HashMap<K, V>, BTreeMap<K, V>.

    These are not contiguous, so you cannot slice them directly. Instead, you use iterators or collect into a Vec to slice.

    use std::collections::HashMap;
    
    fn main() {
        let mut map = HashMap::new();
        map.insert("a", 1);
        map.insert("b", 2);
        map.insert("c", 3);
    
        // Collect into a Vec for slicing
        let mut entries: Vec<_> = map.iter().collect();
        entries.sort_by_key(|(k, _)| *k); // ensure order before slicing
        let slice = &entries[0..2];
        println!("{:?}", slice); // e.g. [("a", 1), ("b", 2)]
    }

    3. Set Collections

    Examples: HashSet<T>, BTreeSet<T>.

    Like maps, sets are not contiguous, but you can convert them to a Vec and then slice.

    
    use std::collections::HashSet;
    
    fn main() {
        let set: HashSet<i32> = [1, 2, 3, 4, 5].iter().cloned().collect();
    
        // Collect into Vec for slicing
        let mut values: Vec<_> = set.iter().collect();
        values.sort(); // ensure stable order
        let slice = &values[1..4];
        println!("{:?}", slice);
    }

    4. Linked Collections

    Examples: LinkedList<T>.

    Linked structures are inherently non-contiguous, so slicing is not supported. Instead, you rely on iteration or manual traversal.

    
    use std::collections::LinkedList;
    
    fn main() {
        let mut list: LinkedList<i32> = LinkedList::new();
        list.push_back(10);
        list.push_back(20);
        list.push_back(30);
    
        // Collect into Vec for slicing
        let values: Vec<_> = list.iter().collect();
        let slice = &values[0..2];
        println!("{:?}", slice); // [&10, &20]
    }

    Summary

    • Sequences (arrays, Vec, String): Direct slicing supported.

    • Maps, Sets, Linked structures: No direct slicing. Must iterate or collect into a Vec first, then slice.

    As you can see, this shows how slicing is tightly connected to contiguous memory layouts, which is why only certain groups of collections support it directly — in other words, you would have to convert those data types first before slicing.

    Now you know when to use slices and how to use them, and what they really are; feel free to make those modifications to your algorithms and function signatures.

    If you have any questions , let me know if you have any questions.

    Source: Read MoreÂ

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleProvisioning a Cloud Project for Optimizely Configured Commerce
    Next Article How to Build an Advice Generator Chrome Extension with Manifest V3

    Related Posts

    Development

    Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

    September 28, 2025
    Development

    Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

    September 28, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Atlassian Enters Into Definitive Agreement to Acquire DX

    Tech & Work

    AI Agents Now Write Code in Parallel: OpenAI Introduces Codex, a Cloud-Based Coding Agent Inside ChatGPT

    Machine Learning

    TC39 advances numerous proposals at latest meeting

    Development

    Meet BioReason: The World’s First Reasoning Model in Biology that Enables AI to Reason about Genomics like a Biology Expert

    Machine Learning

    Highlights

    Databases

    MongoDB 8.0: Improving Performance, Avoiding Regressions

    April 2, 2025

    MongoDB 8.0 is the most secure, durable, available and performant version of MongoDB yet: it’s…

    The Best Automating Content Creation Tools in 2025

    June 27, 2025

    Salesforce AI Research Introduces New Benchmarks, Guardrails, and Model Architectures to Advance Trustworthy and Capable AI Agents

    May 1, 2025

    Cyble Uncovers RedHook Android Trojan Targeting Vietnamese Users

    July 29, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.