为什么在Rust中不能将两个字符串连接起来而不需要引用其中一个字符串?

14

这有效:

let hello = "Hello ".to_string();
let world = "world!";

let hello_world = hello + world;
但是这个不会:
let hello = "Hello ".to_string();
let world = "world!".to_string();

let hello_world = hello + world;

但是这个确实会:

let hello = "Hello ".to_string();
let world = "world!".to_string();

let hello_world = hello + &world;

是否因为String需要指向第二个String的原始字符串切片的指针?如果是这样,为什么呢?


2个回答

15

答案分为两部分。


第一部分是使用Add trait实现来涉及+。它仅适用于以下情况:

impl<'a> Add<&'a str> for String
因此,仅在以下情况下字符串连接才起作用:
  • 左侧是一个String
  • 右侧可以强制转换为&str

注意:与许多其他语言不同,加法操作将消耗左侧参数。


因此,第二部分是关于期望使用&str时可以使用哪些参数的类型?

显然,可以直接使用&str

let hello = "Hello ".to_string();
let hello_world = hello + "world!";
否则,对实现了Deref<&str>的类型的引用将起作用,而String正好实现了这个接口,因此&String也能起作用:
let hello = "Hello ".to_string();
let world = "world!".to_string();

let hello_world = hello + &world;

其他实现呢?它们都有问题。

  • impl<'a> Add<String> for &'a str需要在前面添加,这比追加效率低
  • impl Add<String> for String 没有必要消耗两个参数,一个就足够了
  • impl<'a, 'b> Add<&'a str> for &'b str 隐藏了无条件的内存分配

最终,这种非对称的选择可以通过 Rust 哲学中尽可能明确来解释。

或者更明确地说,我们可以通过检查操作的算法复杂度来解释这个选择。假设左侧大小为 M,右侧大小为 N,则:

  • impl<'a> Add<&'a str> for String 是 O(N)(摊销)
  • impl<'a> Add<String> for &'a str 是 O(M+N)
  • impl<'a, 'b> Add<&'a str> for &'b str 是 O(M+N)
  • impl Add<String> for String 是 O(N)(摊销)……但是需要为此额外分配/克隆右侧。

4

标准库实现了以下功能:

impl<'a> Add<&'a str> for String

正如你所注意到的那样,你只能将&str添加到String中。

这有以下优点:

  • 使用&str片段
  • 使用&String(因为它会解引用为&str
  • 保留已添加的旧字符串

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接