编程全栈开发工程师

Rust中不可变字符串和动态字符串类型String的工作方式是什么?String和&str有什么区别,所有权是如何工作的,以及如何安全地在这些类型之间转换?

用 Hintsage AI 助手通过面试

回答。

问题背景:

与原生语言(C/C++)相比,Rust通过严格区分引用类型(&str)和拥有类型(String)来构建安全的字符串处理。这消除了大多数与内存管理、缓冲区溢出及双重释放相关的错误。

问题:

与成年GC语言不同,在这些语言中,任何字符串都存在于受管理的内存中,Rust需要明确知道谁拥有字符串,它存在多长时间,以及如何在修改后避免悬挂引用。处理UTF-8字符串时,在索引和修改时需要特别小心。

解决方案:

在Rust中,String是一种可变的、堆分配的字符串,拥有自己的内容。&str是对字节序列的不可变引用,并具有UTF-8的保证。如有必要,可以通过标准库的方法安全地进行转换(&str -> String及反之亦然)。

代码示例:

fn main() { let owned: String = String::from("Rust"); let borrowed: &str = &owned; let primitive: &str = "Hello"; // 文本字面量总是&str // 转换 &str -> String let s: String = primitive.to_string(); // 转换 String -> &str let st: &str = &s; println!("{} {} {} {}", owned, borrowed, primitive, st); }

关键特性:

  • 明确区分所有权与引用(堆与切片)
  • String与&str之间的安全转换方法有效且透明,生命周期清晰
  • 字符串字面量总是具有类型&'static str,而非String

诡计问题。

为什么不能像s[1]或s[i]那样索引字符串?

Rust中的字符串是UTF-8的,因此无法直接索引:s[i]不会返回第i个字符,有时会在访问不正确的字节边界时导致panic。请使用方法.chars().nth(i).get(start..end)

可以安全地修改&str吗?

不可以——&str始终是不可变切片。要进行修改,请使用to_owned/to_string,或使用String/Vec<u8>

String::from("abc")与"abc".to_string()有什么根本区别?

这两种方式的结果是等价的,都是通过复制数据从&str创建String。不同之处仅在于风格:例如,to_string是通过Trait ToString实现的,而String::from更清晰地表达了“创建所有权”的意图。

常见错误与反模式

  • 尝试通过索引字符串s[1]或s[0]获取char
  • 在没有指定生命周期的情况下进行隐式转换:返回对临时对象的引用
  • 在可以使用&str的地方使用String(多余的分配)

生活中的例子

消极案例

函数接受String并在内部进行不必要的字符串复制(clone),然后将切片写入其他函数,忘记延长来源的生命周期。结果:悬挂引用&崩溃。

优点:

  • 类似于熟悉的语言:可以轻松获得字符串的“副本”。

缺点:

  • 由于多余的分配导致性能损失
  • 可能会导致对临时值的引用泄漏。

积极案例

函数接受&str,如果需要修改,则在内部调用.to_string(),外部逻辑保持“零复制”。生命周期得到控制,没有多余的分配。

优点:

  • 高性能
  • 避免所有权错误

缺点:

  • 需要理解生命周期和所有权
  • 对初学者的认知负担稍大。