Rust 常用标准库介绍

2022-02-19 rust language

String

核心只有 str 这一种字符串,而且通常以借用 &str 方式使用,例如 let s = "Hello"; 中的 s 就是 &str 类型,如果使用切片,其返回的也是 &str 类型。

String 是标准库一个可变长的实现(是 Vec<u8> 的封装),两者都是通过 UTF8 编码,这是 Unicode 的一种编码形式,采用变长编码。

要理解编码需要区分如下几个概念:

  • 字节 Byte,计算机以 Byte 方式保存的实现,是最底层的实现,因为UTF8是1~4字节变长编码,所以一个可见字符对应1~4个字节。
  • 标量值 Scalar Values,也就是去除高位和低位代理的 Unicode 码位,可以简单理解为码位,也就是类似 U+03a9 这种表示方式。
  • 字形簇 Grapheme Clusters,这才是最终看到的字符,大部分的字符都可以通过一个标量值表示,但是有些语言会使用多个标量值顺序表示一个字符。

因为字符串采用的是变长编码,对字符串的索引是不支持的,如果一个字符是通过 3 字节编码,那么选取其中一个字节是没有意义的,所以如下的代码会报错。

let s = String::from("hello");
let h = s[0];

上述代码编译阶段会报 String` cannot be indexed by `{integer} 这种错误。另外,切片会在运行时检查,可能会出现 panic 错误。

fn main() {
    let hey = "你好";
    let s = &hey[0..3]; // OK
    //let s = &hey[0..4]; // Panic
    println!("{}", s);
}

编译阶段可以通过,但是会在运行时 Panic,报错为 byte index 4 is not a char boundary 。标准库提供了对字节、标量值的遍历方式,并未提供字形簇的遍历,可以参考一些三方 crate 的实现。

fn main() { 
    let word = String::from("你好");
    for byte in word.bytes() {
        println!("{}", byte);
    }
    for scalar in word.chars() {
        println!("{}", scalar);
    }
}

Vec

简单的 array 可以通过 let mut arr = [0 as u8; 64] 这种方式定义,但是无法动态扩展,相对来说 vec 要更加灵活。

用来保存相同类型元素的动态数组,保存元素的类型必须要相同,不过可以通过 enum 实现不同类型的存储。由三部分组成:A) 指向堆保存数据的指针;B) 数组长度;C) 当前容量。

fn main() {
    let mut vec1: Vec<i32> = Vec::with_capacity(6); // or Vec::new()
    let mut vec2 = vec![3, 4, 5];
    vec1.push(1);             // [1]
    vec1.append(&mut vec2);   // [1, 3, 4, 5]
    vec1.pop();               // rc=Some(5) [1, 3, 4]
    vec1.remove(1);           // [1, 4]
    vec1.insert(1, 3);        // [1, 3, 4]
    vec1.resize(8, 0);        // expend [1, 3, 4, 0, 0, 0, 0, 0]
    vec1.resize(2, 0);        // shrink [1, 3]
    for elem in vec1.iter() { // readonly
        println!("{}", elem);
    }
    for elem in vec1.iter_mut() { // writable [2, 6, 8]
        *elem = *elem * 2;
    }
    println!("data={:?} len={} cap={} empty={} 4th={:?} has8={}", vec1, vec1.len(), vec1.capacity(),
        vec1.is_empty(), vec1.get(4), vec1.contains(&8));
}

创建时可以指定容量,不足会自动扩容,访问对应元素时,可以通过 vec[1] 进行访问,但是如果索引不存在,那么就会 panic 掉,建议使用 vec.get(1) 获取,此时会返回 Option 信息。

Rc VS. Arc

Rust 的所有权要求一个对象只能有一个所有者,但是多线程并发编程可能出现持有一份数据的问题,可以通过引用计数解决,有两种方案:A) Rc Reference Counting 引用计数;B) Arc Atomic Rc 原子引用计数;分别用于单线程和多线程版本。

如下是一个所有权的典型报错。

#[allow(unused)]
fn main() { 
    let s = String::from("Hello");
    let a = Box::new(s); // s的所有权转移给了a
    let b = Box::new(s); // 报use of moved value错误
}

可以通过引用计数修改为。

use std::rc::Rc;

#[allow(unused)]
fn main() {
    let s = String::from("Hello");
    let a = Rc::new(s);    // s的所有权转移给了a
    let b = Rc::clone(&a); // 等价于a.clone(),非深拷贝,只是增加引用计数,没有复制底层数据
    println!("Current {}", Rc::strong_count(&a));
}

如下是一个多线程中的示例。

use std::rc::Rc;
use std::thread;

fn main() {
    let s = Rc::new(String::from("Hello World"));
    for _ in 0..10 {
        let s = Rc::clone(&s);
        thread::spawn(move || {
            println!("{}", s)
        });
    }
}

编译会报 the trait `Send` is not implemented for `Rc<String>` 的错误,看着是因为没有实现 Send 特征,无法在线程间安全传递,实际上 Rc 中的引用计数并没有使用任何并发原语,无法实现原子性计数,多线程中会导致错误

修复也很简单。

use std::sync::Arc;
use std::thread;

fn main() {
    let s = Arc::new(String::from("Hello World"));
    for _ in 0..10 {
        let s = Arc::clone(&s);
        thread::spawn(move || { 
            println!("{}", s)
        });
    }
}

原因在于通过原子或者锁实现线程安全,这样同时会带来性能损耗,所以,相当于将最终的决策权交给了用户。

总结

  • Rc/Arc 包含的是不可变引用,无法修改只能读取。
  • 当最后一个拥有者消失时,对应的资源会被回收,所以声明周期在编译阶段已经确定。