为什么在Rust中字符串字面值是&str而不是String?

18

我只是想问为什么Rust决定使用&str而不是String来表示字符串字面值。难道Rust不可能自动将字符串字面值转换为String并将其放在堆上,而不是将其放在栈上吗?


2
理论上可以,但速度会慢得多,而且有什么优势呢? - trent
4
将其放置在堆上而不是将其放入栈中。我猜测字符串字面量被放置在只读数据段(ro.data section)。 - MaxV
3
关于关闭投票,我不同意这个问题会导致基于观点的答案。设计有非常明确的原因。 - Peter Hall
2个回答

22
为了理解这个推理,考虑 Rust 想要成为一种系统编程语言。通常情况下,这意味着它需要尽可能地高效,并让程序员完全控制堆内存的分配和释放(除其他事项之外)。 Rust 的一个用例是嵌入式编程,其中内存非常有限。
因此,Rust 不想在没有严格必要的情况下分配堆内存。字符串文字在编译时已知,并且可以写入可执行文件/库的 ro.data 部分,因此它们不会消耗堆栈空间或堆内存空间。
现在,鉴于 Rust 不想在堆上分配值,它基本上被迫将字符串字面量视为 &str:String 拥有其值并且可以移动和删除,但如何删除存在于 ro.data 中的值呢?实际上无法做到这一点,因此 &str 是完美匹配。
此外,将字符串字面量视为 &str(或更准确地说是 &'static str)具有所有优点而没有缺点。它们可以在多个位置使用,可以共享而不必担心使用堆内存,永远不必删除。此外,它们可以随时转换为拥有的 String,因此始终可以使用它们作为 String,但仅在需要时才需付出成本。

为什么ro.data中的值无法删除是个问题?Rust能否假装它已经被删除并继续执行,还是这样会引起问题?(编辑:我实际上想知道为什么str存在,字符串字面量似乎是答案的重要部分。) - BlackShift
1
问题在于String拥有数据并在它自己被放下时丢弃该数据。可能可以检查它所拥有的数据是否在ro.data中(尽管跨平台可能也很困难),然后避免掉落,但这会使实现更加复杂。 类型StringstrVec<T>[T]中都有对应的类型。由于Rust的所有权模型和共享引用,你真的需要像&str这样的东西,不仅仅是因为字符串字面量。 - Paul
1
另外,请注意 String 拥有并可以修改其数据,而不是你想要在 ro.data 的字符串字面量/对象上进行的操作。 - Paul
谢谢@Paul。我不知道即使是非可变字符串也必须假定其数据是可写的(因为它可以被其所有者变成可变)。而且字符串字面量必须是只读的,出于性能原因(因为它们可以保持在ro.data中)。因此,必须有两种类型来表示字符串。 - BlackShift
嗨,我是新手。你说字符串字面量没有任何缺点。关于反向工程 Rust 代码,字符串字面量是否比 String 更容易进行反向工程?我对反向工程并不是很了解,但我总是希望程序更难以被反向工程。 - sgon00
@sgon00 我不知道 - 我不是一个逆向工程专家。 - Paul

11
要创建一个字符串,你需要:
  • 在堆上分配空间(allocate),
  • 将所需的内容从只读位置复制到新分配的区域。
如果像“foo”这样的字符串字面量两者都做了,那么每个字符串实际上都会被分配两次:一次在可执行文件内作为只读字符串,另一次在堆上。你无法简单地引用存储在可执行文件中的原始只读数据。
"&str" 字面量让你访问最有效的字符串数据:即出现在编译器随着程序指令一起放入可执行映像中的数据。它指向的数据不会被存储在堆栈上,堆栈上只是指针/大小对,就像任何 Rust 切片一样。
让 "foo" 变成现在拼写为 "foo".to_owned() 会使它变慢和占用更多的空间,并且可能需要另一种语法来获得非分配的 "&str"。毕竟,你不想让 x == "foo" 分配一个字符串,然后立即把它扔掉。像 Python 这样的语言通过使其字符串不可变来缓存源代码中提到的字符串。在 Rust 中,改变 String 经常是创建它的整个目的,所以这种策略行不通。

为什么不行呢?我认为只要字符串不是可变的,就可以引用存储在可执行文件中的原始只读数据。如果数据需要被修改,那么它必须被复制。因此,使用字符串似乎都可以而不会有任何惩罚。我错了吗? - BlackShift
1
@BlackShift 为什么不行? - 因为String保证引用堆分配的数据。你甚至可以将其转换为Box<str>Vec<u8>,而无需重新分配内存。我认为只要String不是mut,这应该是可能的。 - 没有所谓的非mut String - 只要你拥有它,你总是可以使其变成mut - user4815162342
谢谢@user4815162342。我不知道你可以将不可变变量变为可变。我猜这是有道理的,因为不可变性/常量的一个重要原因是确保程序的不同部分不会破坏其他部分依赖的数据。Rust已经解决了这个问题。 - BlackShift
1
是的,我想这一定是发生了什么,所以我想要证明它(并在此分享证明以供未来读者参考)。 - BlackShift
我在寻找能够直接修改 str 的函数,看看如果对字符串字面量调用这些函数会发生什么。显然这样的函数是存在的,例如 make_ascii_uppercase(),它需要一个 &mut str。但是,我唯一能够创建 &mut str 的方法是通过像 StringBox 这样的堆分配结构复制 &str。因为 let mut s = s;&str 上不起作用,因为“它在 & 引用后面”。因此,似乎不可能在字符串字面量上调用这样的原地修改函数。 - BlackShift
显示剩余2条评论

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