Easy Tutorial
❮ Home Rust Setup ❯

Rust Collections and Strings

Collections are the most common form of data storage in data structures. The Rust standard library provides a variety of collection types to help developers handle data structure operations.

Vectors

A vector is a single data structure that stores multiple values, which are linearly stored in memory of the same type.

Vectors are linear lists, represented in Rust as Vec<T>.

The usage of vectors is similar to lists. We can create vectors of a specified type in this way:

let vector: Vec<i32> = Vec::new(); // Creates an empty vector of type i32
let vector = vec![1, 2, 4, 8];     // Creates a vector from an array

We often use append operations with linear lists, but since appending is essentially the same as the push operation of a stack, vectors only have the push method to append a single element:

Example

fn main() {
    let mut vector = vec![1, 2, 4, 8];
    vector.push(16);
    vector.push(32);
    vector.push(64);
    println!("{:?}", vector);
}

Running result:

[1, 2, 4, 8, 16, 32, 64]

The append method is used to concatenate one vector to the end of another:

Example

fn main() {
    let mut v1: Vec<i32> = vec![1, 2, 4, 8];
    let mut v2: Vec<i32> = vec![16, 32, 64];
    v1.append(&mut v2);
    println!("{:?}", v1);
}

Running result:

[1, 2, 4, 8, 16, 32, 64]

The get method is used to retrieve values from a vector:

Example

fn main() {
    let mut v = vec![1, 2, 4, 8];
    println!("{}", match v.get(0) {
        Some(value) => value.to_string(),
        None => "None".to_string()
    });
}

Running result:

1

Since the length of a vector cannot be logically inferred, the get method cannot guarantee a value will be retrieved, so its return value is an Option enum, which might be empty.

This is a safe way to retrieve values, but it is somewhat cumbersome to write. If you can ensure that the index for retrieval does not exceed the vector's range, you can also use array indexing syntax:

Example

fn main() {
    let v = vec![1, 2, 4, 8];
    println!("{}", v[1]);
}

Running result:

2

However, if we attempt to get v[4], the vector will return an error.

Iterating over a vector:

Example

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{}", i);
    }
}

Running result:

100
32
57

If you need to change the value of variables during iteration:

Example

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

Strings

The String class has been used extensively up to this point, so many of its methods are already familiar to the reader. This chapter mainly introduces the methods of strings and their UTF-8 properties.

Creating a new string:

let string = String::new();

Converting basic types to strings:

let one = 1.to_string();         // Integer to string
let float = 1.3.to_string();     // Float to string
let slice = "slice".to_string(); // String slice to string

Strings containing UTF-8 characters:

let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("Hello");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");

String Concatenation:

let mut s = String::from("run");
s.push_str("oob"); // Appends a string slice
s.push('!');       // Appends a character

Concatenating Strings with the + Operator:

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;

This syntax can also include string slices:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = s1 + "-" + &s2 + "-" + &s3;

Using the format! Macro:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);

String Length:

let s = "hello";
let len = s.len();

Here, len is 5.

let s = "你好";
let len = s.len();

Here, len is 6 because Chinese characters are UTF-8 encoded, each character is 3 bytes long, so the length is 6. However, Rust supports UTF-8 character objects, so to count the number of characters, you can convert the string to a collection of characters:

let s = "hello你好";
let len = s.chars().count();

Here, len is 7 because there are a total of 7 characters. Counting characters is much slower than counting data length.

Iterating Over a String:

fn main() {
    let s = String::from("hello中文");
    for c in s.chars() {
        println!("{}", c);
    }
}

Output:

h
e
l
l
o
中
文

Extracting a Single Character from a String:

fn main() {
    let s = String::from("EN中文");
    let a = s.chars().nth(2);
    println!("{:?}", a);
}

Output:

Some('中')

Note: The nth function is a method to get a value from an iterator, do not use it this way in a loop! Because UTF-8 characters may not be of equal length!

fn main() {
    let s = String::from("EN中文");
    let sub = &s[0..2];
    println!("{}", sub);
}

Output:

EN

However, be aware that this usage might split a UTF-8 character, which would cause an error:

fn main() {
    let s = String::from("EN中文");
    let sub = &s[0..3];
    println!("{}", sub);
}

Output:

thread 'main' panicked at 'byte index 3 is not a char boundary; it is inside '中' (bytes 2..5) of `EN中文`', src\libcore\str\mod.rs:2069:5 
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Map

Maps are widely used in other languages. The most common type is the key-value hash map.

Creating a New Hash Map:

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    println!("{}", map.get("color").unwrap());
}

Note: The generic type for the hash map is not declared here due to Rust's type inference mechanism.

Execution result:

red

The insert and get methods are the most commonly used methods for a map.

Maps support iterators:

Example

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    for p in map.iter() {
        println!("{:?}", p);
    }
}

Execution result:

("color", "red")
("size", "10 m^2")

Iterated elements are tuples representing key-value pairs.

Rust's map is a very convenient data structure. When using the insert method to add new key-value pairs, if a key already exists, it will directly overwrite the corresponding value. If you want to "safely insert" a key-value pair only if the key does not already exist, you can do this:

map.entry("color").or_insert("red");

This means that if there is no key-value pair with the key "color", it will be added with the value "red"; otherwise, it will be skipped.

If you are sure that a key exists and want to directly modify its corresponding value, there is a faster way:

Example

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, "a");

    if let Some(x) = map.get_mut(&1) {
        *x = "b";
    }
}
❮ Home Rust Setup ❯