为啥str主要存在于其借来的? [复制]

Posted

技术标签:

【中文标题】为啥str主要存在于其借来的? [复制]【英文标题】:Why does str primarily exist in it's borrowed form? [duplicate]为什么str主要存在于其借来的? [复制] 【发布时间】:2020-07-25 05:10:22 【问题描述】:

str 类型是这样使用的:

let hello = "Hello, world!";

// with an explicit type annotation
let hello: &'static str = "Hello, world!";

let hello: str = "Hello, world!"; 导致expected `str`, found `&str`

为什么文本的默认类型不只是 str 与所有原始类型、向量和 String 不同?为什么是参考?

【问题讨论】:

&str 表示它是一个引用(AKA 指针)。典型的编译器会将所有字符串常量(多个字节)放入二进制文件中,传递的只是指向该数据的指针。这与 C 中的情况相同,您通常没有 char[32] 而是将 char* 作为变量的类型。 我没有任何官方来源,但我认为这可能是因为str 是唯一没有实现Copy 的原语。因此,对于整数类型,bool 等,让它们成为引用或拥有生命周期根本没有意义,因为它们可以通过从可执行文件中复制而归上下文所有,而不会出现运行时问题。但是str 的情况并非如此,因为克隆它并不便宜,您几乎总是希望引用它或使其成为可以正确修改的完整字符串。 @MatthiasWimmer 谢谢,你能解释一下“在二进制文件中”是什么意思吗?不是机器级别的所有代码都是二进制的吗? @msrd0 还有其他原语没有实现Copy,包括slices 和mutable references,以及arrays 和tuples,如果它们的元素没有实现它. @Frxstrem 好吧,引用已经是一种引用,但我同意这同样适用于数组和元组,而不仅仅是字符串 【参考方案1】:

字符串和切片只能通过引用访问的设计决策有很多优点:

    字符串可以有任意长度。所以str 类型的变量不容易在堆栈上管理,而&str 仅具有堆栈上指针的大小(而可变长度数据驻留在堆上)。请注意,所有其他原始类型都有固定长度,每个引用都有固定长度(不是它指向的数据)和每个结构(它是一个组合)。 &str 是一个不可变的引用。如果您可以定义str 类型的变量,则必须将语义赋予let mut s: str = "str";。堆栈上的不可变字符串很难管理,可以附加的字符串更难管理。 拥有str 意味着每一步都必须复制所有字符,这会降低性能。只是复制引用并在堆上保持引用数据不变更便宜。这并不是真正的零成本抽象。 str 不是唯一仅作为引用出现的类型 &str(同样适用于切片,如 &[i8]),因此对字符串处理的更改会使其他行为变得奇怪(或者必须相应地更改)。 让我们假设一个函数可以管理str 类型的变量。现在你想从这个函数返回一个&str。这是行不通的,因为引用最多只存在于它指向的值(尝试使用任何原始类型)。由于str 是一个本地创建的值,因此它不能超过该函数的寿命。字符串文字始终是对静态字符串的引用的便利解决了这个问题。这意味着您必须编写额外的代码来将您拥有的str 放入一个静态变量中,这样您就可以返回&str。而且由于静态引用是我需要的默认行为,因此我可以用很少的开销编写它非常方便。

【讨论】:

首先,非常感谢您的回复。关于您的第一点,这与 String 有何不同?那有你在拥有时提到的财产。关于第3点,我很困惑为什么会这样? str 是否正常拥有,然后传递对它的引用 qouldnt cipy 事情对吗? 1. String 确实具有str 的大部分属性。然而,这并不是零成本。如果您需要可变性或所有权,您可以付费使用它,但您也可以使用&str 而不使用它。 3. 移动一个引用(例如&str) means copying a pointer (sometimes the compiler can even omit this). Yet if you keep a hypothetic str` 在堆栈上你将不得不移动/复制值(所有字节)。当然你可以引用这个值 - 但是你的类型是&str。我会添加另一点。 String 实际上更像&str 而不是strString 包含指向其字符串数据的指针,因此移动它很便宜。此外,它具有固定大小,例如&strString&str 之间的唯一区别是 String 拥有其数据,并且您可以在 String 中添加/删除字符。 我不确定如何最好地将其放入您的答案中,但 &str 并不总是指代堆。它可能在文本段(字符串文字)或堆栈中(例如,[u8; N] 数组转换为 str)。【参考方案2】:

我会尝试给出不同的观点。在 Rust 中,有一个通用约定:如果您有某种类型的变量 T,则意味着您拥有与 T 关联的数据。如果你有一个&T 类型的变量,那么你不拥有数据。

现在让我们考虑一个堆分配的字符串。根据这个约定,应该有一个代表分配所有权的非引用类型。确实存在这样的类型:String

还有一种不同的字符串:&'static str。这些字符串不属于任何人:只有一个字符串实例被放置在编译的二进制文件中,并且只有指针被传递。没有分配也没有解除分配,因此没有所有权。从某种意义上说,静态字符串归编译器所有,而不是程序员所有。这就是为什么String不能用来表示静态字符串的原因。

好吧,那为什么不用&String 来表示一个静态字符串呢?想象一个世界,下面的代码是一个有效的 Rust:

let s: &'static String = "hello, world!";

这可能看起来不错,但在实现方面,这是次优的:

    String 本身有一个指向实际数据的指针,所以 &String 基本上必须是一个指向指针的指针。这违反了零成本抽象原则:为什么我们要引入过多的间接级别,而实际上编译器静态知道"hello, world!" 的地址?

    即使编译器以某种方式足够聪明地决定这里不需要过多的指针(这会导致一堆其他问题),String 本身仍然包含三个 8 字节字段:

    数据指针; 数据长度; 分配容量 - 让我们知道数据后还有多少可用空间。

    然而,当我们谈论静态字符串时,容量是零意义的:静态字符串是只读的

所以,最后,当编译器看到&'static String时,我们实际上希望它只存储一个数据指针和长度——否则,我们正在为我们永远不会使用的东西付费,这违反了零成本抽象原则.这看起来像是我们想要从编译器获得的神秘魔法:变量类型是 &String,但变量本身不是对 String 的引用。

为了完成这项工作,我们实际上需要一个不同的类型,而不是&String,它只保存一个数据指针和长度。这里是:&str!它在很多方面都优于&String

    没有过多的间接级别 - 只有一个指针; 不存储容量,这在许多情况下毫无意义; 没有黑魔法:我们将str 定义为可变大小的类型(数据本身),因此&str 只是对数据的引用。

现在您可能想知道:为什么不介绍str 而不是&str?请记住约定:拥有str 意味着您拥有数据,而您并不拥有。因此&str

【讨论】:

以上是关于为啥str主要存在于其借来的? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥我仍然可以在字符串范围之外访问 std::string::c_str() 返回的 char 指针? [复制]

为啥没有在 python3 中检查返回类型? [复制]

为啥 Enumerable.All 对空序列返回 true? [复制]

为啥 double 和 float 存在? [复制]

即使路径不存在,为啥 Path(...).exists 为真? [复制]

为啥使用 copy() 时字符串和数组的处理方式不同? [复制]