跳到主要内容

Rust中的字符串类型:&str与String详解

一、&str的内存管理与生命周期

1. 内存管理机制

(1) 字符串字面量的&str

let s: &str = "hello"; // 字符串字面量
  • 存储在静态只读内存区域
  • 程序运行期间始终有效
  • 不占用堆内存,仅包含指针和长度

(2) 堆分配字符串的切片引用

let s = String::from("hello");
let slice: &str = &s;
  • 生命周期与源String绑定
  • 当String被释放时切片自动失效
  • Rust编译器防止悬垂引用

(3) 临时值的引用

let slice: &str = &String::from("hello"); // 编译错误示例
  • 临时值在语句结束时释放
  • 编译器会阻止此类不安全引用

2. 生命周期特性

  • 静态生命周期:&'static str
  • 动态绑定生命周期:依赖被引用数据的作用域
  • 编译器通过生命周期标注确保引用有效性

3. 无需回收的原因

  • 本质是数据的引用指针
  • 不拥有数据所有权
  • 内存管理由被引用数据负责

4. 生命周期示例

// 静态生命周期
fn static_str() -> &'static str {
"hello"
}

// 动态绑定示例
fn print_str(s: &str) {
println!("{}", s);
}

二、&str与String的核心区别

1. 基本特性对比

特性&strString
存储位置栈/静态区
可变性不可变可变
所有权无所有权拥有所有权
内存管理引用计数自动分配/释放
典型用途函数参数/字面量动态构建字符串

2. 代码示例对比

不可变性差异

// &str不可变示例
let s = "hello";
// s.push_str(" world"); // 编译错误

// String可变示例
let mut s = String::from("hello");
s.push_str(" world"); // 合法操作

所有权传递

fn take_ownership(s: String) {
println!("Got ownership of: {}", s);
}

fn borrow_str(s: &str) {
println!("Borrowed: {}", s);
}

3. 类型转换

String转&str

let s = String::from("hello");
let slice: &str = &s; // 自动转换

&str转String

let s = "hello";
let s1 = String::from(s);
let s2 = s.to_string();

4. 性能特征

  • &str操作零内存分配
  • String涉及堆内存管理
  • 切片操作比克隆更高效

5. 使用场景建议

推荐使用&str的场景

  • 函数参数传递
  • 只读数据访问
  • 模式匹配操作
  • 组合字符串片段

推荐使用String的场景

  • 动态构建复杂字符串
  • 需要所有权转移
  • 长期存储字符串数据
  • 跨线程传递数据

三、最佳实践建议

  1. 参数传递优先

    // 良好实践
    fn process_data(data: &str) {
    // 处理逻辑
    }
  2. 适时转换类型

    let input = "initial".to_string();
    let trimmed = input.trim().to_string();
  3. 生命周期管理

    fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() { s1 } else { s2 }
    }
  4. 避免不必要的转换

    // 直接使用切片
    let s = String::from("hello");
    let sub = &s[0..2];
  5. 模式匹配优化

    match some_str {
    "" => println!("Empty"),
    s => println!("Content: {}", s),
    }