Skip to main content

String

  • Rust 开发者经常会被字符串困扰的原因

    • Rust 倾向于暴露可能的错误
    • 字符串数据结构复杂
    • UTF8
  • 字符串是什么

    • Byte 的集合
    • 一些方法
      • Byte 解析为文本
    • Rust 的核心语言层面,只有一个字符串类型,字符串切片 str (或 &str
    • 字符串切片:对存储在其他地方、UTF8 编码的字符串的引用
      • 字符串字面值:存储在二进制文件中,也是字符串切片
  • String 类型

    • 来自标准库 而不是 核心语言
    • 可增长、可修改、可获得所有权的类型
    • UTF8 编码
  • 通常说的字符串是指

  • String 和 &str

    • 在标准库里面用的多
    • UTF8 编码
  • Rust 的标准库还包含了很多其他的字符串类型,例如 OsString、OsStr、CString、CStr

    • String vs Str 后缀:可获得所有权 或 可借用的 变体
    • 可存储不同编码的文本或在内存中的形式展现是不同的
  • Library crate 针对存储字符串可提供更多的选项

创建一个新的字符串

String 就是个 Byte 的集合

  • 很多 Vec<T> 的操作都可用于 String
  • String::new() 函数

fn main(){
let mut s = String::new();
}

  • 使用初始值来创建 String:
    • to_string() 方法,可用于实现了 Display Trait 的类型,包括字符串字面值。
    • String::from()函数,从字面值创建 String。

let data = "initial contents";
let s = data.to_string();
let s1 = "initial contents".to_string();

更新 String

它的大小是可以增减的,里面的内容也可以修改,它的操作就像操作 Vector 一样,此外还可以对 String 进行拼接。

  • push_str() 方法:把一个字符串切片附加到 String(例子)。

fn main() {
let mut s = String::from("Hello");
let s1 = String::from(" World");
s.push_str(&s1);
println!("{}, {}", s, s1);
}

  • push() 方法:把单个字符附加到 String
fn main() {
let mut s = String::from("Hello");
s.push(' ');
s.push('W');
s.push('o');
s.push('r');
s.push('l');
s.push('d');
println!("{}", s);
}

+ 拼接字符串

  • 使用了类似这个签名的方法 fn add(self,:&str)-> String{...}
    • 标准库中的 add 方法使用了泛型
    • 只能把 &str 添加到 String
    • 解引用强制转换(deref coercion)

fn main() {
let s1 = String::from("Hello");
let s2 = String::from(" World");

let s3 = s1 + &s2;

println!("s1:{},s2:{}, s3:{}", s1, s2, s3);
}

理解上文的字符串拼接
  • 字符串拼接除去第一个,剩下的都必须是字符串切片的类型。
  • deref coercion:它把 String 的引用转化成了字符串切片,所以编译就能通过,因为第二个字符串切皮有&符号,所以第二个参数的所有权就会保留,所以后续仍然可以使用。
  • s1 没有用到 &符号 所以 add 函数就会取得第一个参数的所有权,所以在 s3 字符串拼接之后,s1 的所有权就交到了 add 函数里面,s1 从此就失效了,所以后面再使用 s1 就会报错。
  • 所以这个字符串拼接的操作是取得了 s1 字符串的所有权,然后把 s2 的内容复制进来组成了一个新的结果并且把这个所有权赋予了 f3。

format!宏链接多个字符串


fn main() {
let s1 = String::from("ACE");
let s2 = String::from("Sabo");
let s3 = String::from("Luffy");

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

println!("{}", s);
}

  • 不会取得任何参数的所有权。
  • 和 println 相似,但返回的是字符串。

对 String 按索引的形式进行访问

  • 按索引语法访问 String 的某部分,会报错(例子)。

  • Rust 的字符串不支持索引语法访问。

  • 内部表示

    • String 是对 Vec<u8> 的包装。
      • len() 方法
      • 每个 Unicode 标量值会占用 2 个字节
演示

fn main() {
let s1 = String::from("Здравствуйте").len();

let s = &s1[0];

println!("{}", s1);
}

当使用 UTF8 编码的时候 Здравствуйте 的第一个字母,它不是 3,就是很像 3 的字母,它实际上是占了两个字节,而这两个字节对应的值呢分别是 208 和 151,假如说这种语法是允许的, 我们就会得到 208 和 151,这个 208 并不是合法的字符,而且用户返回这个 208 也根本没有啥意义,但它确实是索引 0 上的字节,而且即便 208 对应的是合法字符,估计用户也不想得到的是 208, 所以为了避免这种意想不到的情况出现,并且防止引起无法立即发现的 bug,rust 语言就直接拒绝编译这段代码,也就是在开发的早期阶段,杜绝一切有可能产生的误解。

字节 标量值 字形簇

Bytes Scalar Values Grapheme Clusters

  • Rust 有三种看待字符串的方式:

    • 字节
    • 标量值
    • 字形簇(最接近所谓的“字母”)
以字节的方式看待String
fn main() {
let s1 = String::from("नमस्ते"); // 梵文书写的印度单词

for b in s1.bytes() {
println!("{}", b);
}
}

以标量值的方式看待String
fn main() {
let s1 = String::from("नमस्ते"); // 梵文书写的印度单词

for b in s1.chars() {
println!("{}", b);
}
}
  • Rust 不允许对 String 进行索引的最后一个原因:
    • 索引操作应消耗

切割 String

  • 可以使用 [] 和 一个 范围来创建字符串的切片
    • 必须谨慎使用
    • 如果切割时跨越字符边界,程序就会恐慌(panic)
注意
  • Rust 选择将正确处理 String 数据作为所有 Rust 程序的默认行为

    • 程序员必须在处理 UTF8 数据之前投入更多的精力
  • 可防止在开发后期处理涉及非 ASCII 字符的错误。