Skip to main content

String

String 比基础标量类型更复杂

  • JS 的区别是,在 Rust 里面, String 是储存在 Heap 里面的数据类型。

  • 字符串字面值:程序里手写的那些字符串值。他们是不可变的。

  • 不是所有的字符串值都能在编写代码的时候确定,假如说我们要获取用户的输入并保存,这个时候 Rust 提供了第二种字符串的类型 String

    • Heap 上分配存储空间。能够储存在编译时未知大小的文本。
注意

from 函数从字符串字面值创建出 String 类型

fn main(){
let mut s = String::from("hello");
s.push_str(" ace");
println!("{}", s);
}

:: 表示 from 是 String 类型下的函数。

所以像 s 这类的字符串是可以被修改的

  • 为什么 String 类型的值可以修改,而字符串字面值却不能修改?
    • 因为它们处理内存的方式不同。

字符串切片类型 &str

💡 注意:字符串切片的范围索引必须发生在有效的 UTF8 字符边界内。

如果尝试从一个多字节的字符中创建字符串切片,程序会报错并退出,str


fn demo(s: &str) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' { // b 是字符的字面值
return &s[..i]
}
}

&s[..]
}

String 的内存和分配

  • String 字面值,在编译的时候就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里。

    • 所以速度快、高效。是因为其的不可变性。
  • Strng 类型,为了支持可变性,需要在 Heap 上分配内存来保存编译时未知的文本内容:

    • 操作系统必须在运行时来请求内存。
      • 这一步通过调用 String::from 来实现。
    • 当用完 String 之后,需要使用某种方式将内存返回给操作系统。
      • 这步,在拥有 GC 的语言中, GC 会跟踪并处理不再使用的内存。
      • 没有 GC,就需要我们去识别内存何时不再使用,并调用代码将它返回。
        • 如果忘了,那就浪费内存。
        • 如果提前做了,变量就会变成非法。
        • 如果做了两次,也就是非常严重的 Bug。所以针对这类语言,要求每做一次分配,必须对应一次释放。
  • Rust 采用了不同的方式:对于某个值来说,当拥有它的变量走出作用域范围时,内存就会立即自动的交还给操作系统,也就是内存会立即释放。

🌰
fn main(){
let mut s= String::from("ace");
s.push_str(", best");
println!("{}", s);
}

s 离开作用域的时候, Rust 会调用一个叫 drop 这样一个特殊的函数。

变量和数据交互的方式:

移动(Move)

  • 多个变量可以与同一个数据使用一种独特的方式来进行交互。
简单版本
let x = 5;
let y = x;
  1. 首先这个 5 就保存到了变量 x 上。
  2. 然后变量 y 就等于创建了 x 的副本,x 的副本保存到了 y 这个变量上。
  3. 所以,整数是已知且固定大小的简单的值,这两个 5 被压到了 Stack 中。
String 版本
let s1 = String::from("hello");
let s2 = x;
  • 一个 String 类型由 3 个部分组成

s1 这一块数据被压到了 Stack 中,但是字符串内容的部分就存在了箭头右侧的 Heap 中。

  1. 一个指向存放字符串内容的内存的指针
  2. 一个长度 len
    • 存放字符串内容所需要的字节数
  3. 一个容量 capacity
    • 是指 String 从操作系统总共获得内存的总字节数
  • 当把 s1 赋给 s2 , String 的数据被复制了一份。
    • Stack 上复制了一份指针、长度、容量。
    • 并没有复制指针所指向的 Heap 上的数据(箭头右边的数据并没有被复制)。
    • 当变量离开作用域的时候, Rust 会自动调用 drop 函数,并将变量使用的 Heap 内存释放。
    • 当 s1 和 s2 离开作用域的时候,它们都会尝试释放相同的内存。
      • 二次释放(double free)bug。这种二次释放或者是重复释放内存可能会导致某些正在使用的数据发生损坏,进而产生潜在的安全隐患。
  • 为了保证内存安全
    • Rust 没有尝试复制被分配的内存(箭头右侧的内存没有被复制)。
    • Rust 的做法是让 s1 废弃。
      • s1 离开作用域的时候, Rust 不需要释放任何东西。
🌰

当 s2 创建以后,再使用 s1 是什么效果。


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

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

Compiling lesson5 v0.1.0 (D:\Rust\lesson5)
error[E0382]: borrow of moved value: `s1`
--> src\main.rs:5:23
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{},{}", s1, s2);
| ^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0382`.
error: could not compile `lesson5` due to previous error