Rust Collections and Strings

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

Vector

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

A vector is a linear list, represented as Vec<T> in Rust.

Vectors are used in a way similar to lists. We can create a vector of a specified type in this way:

let vector: Vec<i32> = Vec::new(); // 创建类型为 i32 的空向量
let vector = vec![1, 2, 4, 8];     // 通过数组创建向量

When using linear lists, we often need to append operations. However, appending is essentially the same as the stack's push operation, so vectors only have the push method to append a single element:

, the vector will return an error.

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

Output:

[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);
}

Output:

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

The get method is used to retrieve a value 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()
    });
}

Output:

1

Because the length of a vector cannot be logically inferred, the get method cannot guarantee that a value will be returned, so the return type of the get method is the Option enum, which may be None.

This is a safe way to get a value, but it is a bit cumbersome to write. If you can guarantee that the index is within the vector's valid range, you can also use the array indexing syntax:

Example

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

Output:

2

But if we try to access v

Iterating over a vector:

Example

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

Output:

100
32
57

If you need to change the value of a variable during iteration:

Example

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

String

The String type has been used extensively up to this chapter, so many of its methods are already familiar to readers. This chapter mainly introduces string methods and UTF-8 characteristics.

Creating a new string:

let string = String::new();

Converting basic types to strings:

let one = 1.to_string();         // 整数到字符串
let float = 1.3.to_string();     // 浮点数到字符串
let slice = "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("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");

Appending to a string:

let mut s = String::from("run");
s.push_str("oob"); // 追加字符串切片
s.push('!');       // 追加字符

Concatenating strings with the + sign:

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 the value of len is 5.

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

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

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

Here the value of len is 7, because there are 7 characters in total. Counting characters is much slower than getting the byte length.

Iterating over a string:

Example

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

Output:

h
e
l
l
o
中
文

Getting a single character from a string:

Example

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

Output:

Some('中')

Note: The nth function is a method to retrieve a value from an iterator. Please do not use it this way while iterating! Because the length of each character in UTF-8 is not necessarily equal!

If you want to extract a substring from a string:

Example

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

Output:

EN

But note that this usage may split a UTF-8 character! It will cause an error:

Example

fn main() {
    let s = String::from(EN Chinese);
    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 exist widely in other languages. The most commonly used one is the key-value hash map.

Creating a new hash map:

Example

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 reason we didn't declare the generic type of the hash map is because of Rust's automatic type inference mechanism.

Output:

red

The insert and get methods are the two most commonly used methods of 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);
    }
}

Output:

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

The iterated elements are tuples representing key-value pairs.

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

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

This means: if there is no key-value pair with the key "color", add it and set the value to "red"; otherwise, it will be skipped.

If you have confirmed 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";
    }
}
Other extensions