DEV Community

Cover image for A Slice of Rust: Working with the Slice Type
Itachi Uchiha
Itachi Uchiha

Posted on

A Slice of Rust: Working with the Slice Type

This post published on my blog before


Hi everyone. Before that, I wrote a post called Rust’s Borrowing and Reference Laws.

Today I'll try to explain the slice type in Rust

Before starting, I’ll create a project with cargo;

cargo new slice_type

cd slice_type
Enter fullscreen mode Exit fullscreen mode

Introduction

If you worked with languages like Python, you are familiar with slices. Slices don't have ownership in Rust. When you work with slices, you think about a contiguous sequence of an element like a string.

For example, we're using slices in Python like that;

py_string = 'Python'

# contains indices 0, 1 and 2
print(py_string[0:3])  # Pyt
Enter fullscreen mode Exit fullscreen mode

We're calling this as indexing syntax.

Let's Back to the Rust Side

Let's assume we have a function like below. In this section, we'll use Rust's example.

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (index, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return index;
        }
    }

    s.len()
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we've created a function called first_word. It doesn't have ownership. So, that's what we want. The Rust asking us what should we return? Is this a real question? I don't think so. However, in this example, we're trying to return an index or a length value for the whole string.

This function will return a byte index value according to example. We're converting our string to an array of bytes using as_bytes() method.

let bytes = s.as_bytes();
Enter fullscreen mode Exit fullscreen mode

In the above line, we've created an array of bytes from a string. The next line is about iteration. We will not discuss the enumerate() method in this post. For now, we should know that iter() is a method that returns each element in a collection. So, the enumerate() method wraps the result of iter and returns each element as part of a tuple instead.

for (i, &item) in
Enter fullscreen mode Exit fullscreen mode

In the above section, we're destructing a tuple. The first value of tuple is representing index and the second is representing value. Because we get a reference to the element from .iter().enumerate(), we use & in the pattern.

if item == b' ' {
            return index;
        }
Enter fullscreen mode Exit fullscreen mode

In the above lines, we're checking whether the item equals to space or not. For example, if you passed Hello world!, this block will run and return the last index. If you passed Helloworld!, s.len() will return. Let's complete our example;

fn main() {
  let s = String::from("Hello world");

  let index = first_word(&s);

  println!("Index {}", index);
}

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (index, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return index;
        }
    }

    s.len()
}
Enter fullscreen mode Exit fullscreen mode

Guess what will return :) It will return 5. Because in this example, we're looking for a space character. And that space is coming after o.

String Slices

Each string consists of slices. I mean, each character is a slice. Let's assume we have a string like that;

Hello world. We also have slices by this example. These are;

INDEX VALUE
0 H
1 e
2 l
3 l
4 o
5
6 w
7 o
8 r
9 l
10 d

When you want to get a part of a string, you should use a range within brackets by specifying. For example;

let hello = String::from("Hello world");

let start_index = 0;
let last_index = 5;

let hello = &hello[start_index..last_index];
Enter fullscreen mode Exit fullscreen mode

You can also use .get() method. But &T and .get() are different. So, we know how we can get a range from a string. Let's say we want to get a string after the specified letter. How can we do that?

fn main() {
    let s = String::from("Hello world");
    let letter = String::from(" ");

    let index = first_word(&s, &letter);

    println!("The string is {}", &s[index..]);
}

fn first_word(s: &String, l: &String) -> usize {
    let bytes = s.as_bytes();
    let letter = l.as_bytes();

    for (index, &item) in bytes.iter().enumerate() {
        if item == letter[0] {
            return index;
        }
    }

    s.len()
}
Enter fullscreen mode Exit fullscreen mode

That's what we want :) You don't have to specify ending or starting index always. You can use like that;

&your_var[start_index..];

&your_var[..end_index];

&your_var[..];
Enter fullscreen mode Exit fullscreen mode
  • The first example will get string starting the index you specified.
  • The second example will get string until the index you specified.
  • The last example will return the whole string.

Note: String slice range indices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error.

Other Type Slices

String slices were strings. But slices can be used for other types. Let's create a u8 array.

fn main() {
  let number_array = [1, 5, 7, 9, 11, 13, 15, 17, 19, 21];

  println!("Numbers: {:?}", &number_array[1..3]);
}
Enter fullscreen mode Exit fullscreen mode

It will return Numbers: [5, 7].

So, according to this example, we can create other types of slices.

fn main() {
  let an_array = [true, true, false, true, false, false, true, false];

  println!("Array values: {:?}", &an_array[1..3]);
}
Enter fullscreen mode Exit fullscreen mode

That's all for now. If there is something wrong, let me know.

Thanks for reading

Resources

Top comments (1)

Collapse
 
yaroslavpodorvanov profile image
Yaroslav Podorvanov

what type of slice?
will be great to see example function signature with slice argument