Slice Type in Rust

In Rust, there is a concept of borrowing and taking ownership. This is a concept not many languages implement.

Ownership and Borrowing

In a Rust program, any given value is owned by a variable. For instance, if we declare let a = 30;, value 30 would be owned by variable a.

Following is a program to illustrate it:

// won't compile
fn main() {
    let some_string = String::from("hello world");

    println!("Length {}", str_length(some_string));
    println!("string is '{}'", some_string);
}

fn str_length(s: String) -> usize {
    s.len()
}

Taking Ownership

Look at the highlighted codeblock. I am passing the variable some_string to function str_length() which accepts parameter as value. When the function gets called, the value of some_string moves to function str_length, making the variable unusable after the function call(here: println!("string is '{}'", some_string);). This is called taking ownership.

Borrowing

On the other hand: if I pass argument some_string to str_length by reference, the ownership would be retained by some_string. A modified version of str_length signature would look like:

fn str_length(s: &String)

This is known as Borrowing. It is not quite as simple as it looks like. There are certains rules which needs to be followed while borrowing a value. For instance: a variable can be mutated only once at a given scope.

The Slice Type

A slice is reference to a contiguous sequence of elements in a collection. It does not take ownership(since it just references a sequence).

// slice of a string
fn main() {
  let s1 = String::from("hello, Rust");
  let sliced_str = &s1[0..5];  // hello
}

A good thing about slice is that it accepts type String as well as itself (i.e. str type), unlike String(which does not accept str).

In Rust, .. is called as range syntax.

An example would demonstrate it better:

fn main() {
    let str1 = String::from("hello, Rust");

    print_str(&str1[2..4]); // ll
    print_str(&str1);       // hello, Rust
}

fn print_str(s: &str) {
    println!("{}", s);
}

Slice Varients

Slice comes with quite a few varients, which fits different use cases. Here I am listing a few:

Referencing from the start of a Collection: when referencing from the very first element in a collection(i.e. 0), one could ommit it. Example: &s[..4].

Likewise, when referencing upto the last element of a collection, can be omitted. &s[3..].

Similarly, when referencing the whole collection(i.e. from start to end), one can ommit ranges in range syntax. Example: &s[..]

Real-World Example

fn main() {
  let s = String::from("Hello, world");
  let word = get_first_word(&s);

  println!("First word is '{}'", word);
}

fn get_first_word(s: &str) -> &str {
  let str_bytes = s.as_bytes();

  for (i, &item) in str_bytes.iter().enumerate() {
    if item == b' ' {
      return &s[0..i];
    }
  }

  return &s[..];
}