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