为啥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
而不是str
。 String
包含指向其字符串数据的指针,因此移动它很便宜。此外,它具有固定大小,例如&str
。 String
和 &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 指针? [复制]
为啥 Enumerable.All 对空序列返回 true? [复制]