基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Rust中String 和 &str 有什么区别?

知识点图片

Rust 中的 String&str 是初学者最容易混淆的概念之一。简单来说,区别在于 所有权(Ownership)内存管理

可以用一个通俗的比喻:

  • String 就像是你买下来的房子。你拥有它,可以随意装修(修改)、扩建(增加内容),但它比较重,买卖(传递)成本高。
  • &str 就像是透过窗户看房子。你只是在看(引用),不能修改房子结构,而且你看的可能只是房子的一部分(切片)。

详细对比

1. String (所有者 / 堆分配字符串)

  • 定义String 是 Rust 标准库提供的一个结构体(Struct)。
  • 所有权:它拥有数据的所有权。当 String 离开作用域时,数据会被自动清理(Drop)。
  • 内存位置:数据存储在 堆(Heap) 上。
  • 特性
    • 可变:可以修改(push_str, pop 等),前提是变量声明为 mut
    • 可增长:由于在堆上,它可以在运行时动态改变大小。
  • 内部结构:在栈上包含三个字段:
    1. 指向堆内存的指针 (pointer)
    2. 长度 (length)
    3. 容量 (capacity)

2. &str (借用者 / 字符串切片)

  • 定义&str 是 Rust 的原生类型(Primitive Type),通常被称为“字符串切片”(String Slice)。
  • 所有权:它不拥有数据,只是对数据的借用(Borrow)
  • 内存位置:它可以指向 (如果是 String 的切片)或者 静态内存(如果是字符串字面量)。
  • 特性
    • 不可变:你不能通过 &str 去修改原数据。
    • 固定大小:它只是一个视图(View),本身大小固定。
  • 内部结构:它是一个“胖指针”(Fat Pointer),在栈上包含两个字段:
    1. 指向数据的指针 (pointer)
    2. 切片的长度 (length)
      注意:它不知道容量。

内存模型图解

假设我们有以下代码:

plaintext
let s1 = String::from("Hello");
let s2: &str = &s1;

内存中的样子大致如下:

plaintext
      栈 (Stack)                       堆 (Heap)
+-------------------+            +-------------------+
| s1 (String)       |            |                   |
| ptr      -------->|----------->|  'H', 'e', 'l',   |
| len: 5            |            |  'l', 'o'         |
| capacity: 5       |            |                   |
+-------------------+            +-------------------+
                                          ^
+-------------------+                     |
| s2 (&str)         |                     |
| ptr      ---------|---------------------+
| len: 5            |
+-------------------+

核心区别总结表

特性 String &str
所有权 拥有数据 (Owned) 借用数据 (Borrowed)
内存位置 数据在堆 (Heap) 上 数据可以在堆、栈或静态区
可变性 可变 (Mutable) 不可变 (Immutable)
大小 动态大小 (可增长) 运行时长度已知,但不可增长
开销 较重 (涉及内存分配) 轻量 (只是指针和长度)
生命周期 由所有权规则决定 必须带有生命周期 (虽然常被省略)

常见用法与转换

1. 字符串字面量是 &str

plaintext
// "Hello" 存储在编译后的二进制文件中(静态内存)
// s 的类型是 &str (具体说是 &'static str)
let s = "Hello"; 

2. &strString (复制数据,开销大)

当你需要拥有数据,或者需要修改数据时:

plaintext
let slice = "hello";
let s1 = slice.to_string();
let s2 = String::from(slice);
let s3 = slice.to_owned();

3. String&str (零拷贝,开销极小)

当你只需要读取数据时:

plaintext
let s = String::from("hello");
let slice1 = &s;          // 自动解引用强制转换 (Deref Coercion)
let slice2 = &s[..];      // 全切片
let slice3 = s.as_str();  // 显式转换

什么时候用哪个?

作为函数参数时:

  • 推荐使用 &str:如果你只是想读取字符串的内容,不需要获取所有权。这样函数既可以接受 String (通过 &) 也可以接受 &str,灵活性更高。
    plaintext
    // 好:既能传 String 也能传 &str
    fn print_me(text: &str) { println!("{}", text); }
  • 使用 String:如果你需要获取所有权(例如你要把这个字符串存到一个结构体里,或者你要在函数内部把它传给另一个线程)。

作为结构体字段时:

  • 推荐使用 String:这最简单,结构体拥有字符串的所有权。
  • 使用 &str:只有当你确定结构体的生命周期短于字符串数据的生命周期时才用(需要显式标注生命周期 'a),这对初学者来说通常比较复杂。

总结

  • String = 拥有者,堆内存,可变,重。
  • &str = 观察者,引用,不可变,轻。
00:00
00:00