Rust的`String`和`str`有什么区别?


418

为什么Rust有StringstrString和之间有什么区别str?什么时候用String代替,str反之亦然?其中之一是否已被弃用?

Answers:


488

String是动态堆字符串类型,例如Vec:当您需要拥有或修改字符串数据时使用它。

str是内存中某处动态长度的UTF-8字节的不变1序列。由于大小未知,因此只能在指针后面处理它。这意味着str最常见的2表示&str:对某些UTF-8数据的引用,通常称为“字符串切片”或仅称为“切片”。切片只是某些数据的视图,该数据可以在任何地方,例如

  • 在静态存储中:字符串文字"foo"&'static str。程序运行时,数据被硬编码到可执行文件中并加载到内存中。
  • 里面堆分配StringString解除引用到&str视图中的String“s的数据。
  • 在堆栈上:例如,以下创建堆栈分配的字节数组,然后以形式获取该数据&str视图

    use std::str;
    
    let x: &[u8] = &[b'a', b'b', b'c'];
    let stack_str: &str = str::from_utf8(x).unwrap();

总之,String如果需要拥有的字符串数据(例如,将字符串传递到其他线程或在运行时构建它们),请使用;&str如果仅需要字符串的视图,请使用。

这与向量Vec<T>和切片之间的关系相同&[T],并且与常规类型的按值T和按引用之间的关系相似&T


1 A str是定长的;您不能将字节写到末尾,也不能保留尾随的无效字节。由于UTF-8是宽度可变的编码,因此str在许多情况下,这实际上迫使所有s都是不可变的。通常,变异需要写入比以前更多或更少的字节(例如,用(2+字节)替换a(1字节ä)将需要在中留出更多空间str)。有一些可以&str在适当位置修改的特定方法,大多数是仅处理ASCII字符的方法,例如make_ascii_uppercase

2 动态大小类型允许Rc<str>自Rust 1.2起就进行一系列按引用计数的UTF-8字节的操作。Rust 1.21允许轻松创建这些类型。


10
“ UTF-8字节序列(未知长度)”-这是过时的吗?的文档说“A &str是由两个部件组成:一个指向一些字节,和长度。”
mrec

11
并不是过时的(表示已经相当稳定),只是有点不精确:它不是静态已知的,不像说[u8; N]
休恩

2
@mrec在编译时是未知的,例如,在创建堆栈框架时无法假定它的大小。因此为什么经常将其视为引用,而引用是在编译时已知的大小,即指针的大小。
Sekhat '17

1
更新:Rc<str>并且Arc<str>现在可以通过标准库使用。
Centril '18

1
@cjohansson静态分配的对象通常既不存储在堆中也不存储在堆栈中,而是存储在它们自己的内存区域中。
布伦南·文森特

96

我有一个C ++背景,我发现去想它非常有用String,并&str在C ++方面:

  • String像锈std::string; 它拥有内存并完成管理内存的工作。
  • &strchar*(但更复杂);它以指向内容的指针的相同方式将我们指向块的开头std::string

他们两个都会消失吗?我不这么认为。它们有两个作用:

String保留缓冲区,非常实用。&str是轻量级的,应该用于“查看”字符串。您可以搜索,拆分,解析甚至替换块,而无需分配新的内存。

&str可以查看a的内部,String因为它可以指向某些字符串文字。以下代码需要将文字字符串复制到String托管内存中:

let a: String = "hello rust".into();

以下代码可让您直接使用文字本身而不进行复制(尽管只读)

let a: &str = "hello rust";

12
像string_view?
Abhinav Gauniyal

1
是的,像string_view一样,但是是语言固有的,可以适当地检查一下。
Locka

41

str(仅用作&str)是字符串切片,是对UTF-8字节数组的引用。

String是以前的可~str扩展拥有的UTF-8字节数组。


从技术上讲,~str现在是Box<str>
过去了

3
@ jv110:不,因为可~str成长而Box<str>不可成长。(这~str~[T]是可增长的神奇,不同于其他任何~-object,正是为什么StringVec<T>引入,使这些规则都是简单和一致。)
克里斯·摩根

18

它们实际上完全不同。首先,a str只是类型级别的东西;它只能在类型级别进行推理,因为它是所谓的动态大小类型(DST)。str占用的大小在编译时无法知道,并且取决于运行时信息-无法将其存储在变量中,因为编译器需要在编译时知道每个变量的大小。从str概念上讲,A 只是一行u8字节,并保证它形成有效的UTF-8。行有多大?在运行时之前没人知道,因此无法将其存储在变量中。

有趣的是,在运行时确实存在&str指向strlike的指针或任何其他指针。这就是所谓的“胖指针”;它是带有额外信息的指针(在这种情况下,它是指对象的大小),因此它的大小是其两倍。实际上,a 非常接近a (但不接近)。A 是两个字;一个指针指向a的第一个字节,另一个指针描述a是多少字节。Box<str> &strString&String&strstrstr

与所说的相反,a str不必是不变的。如果可以获取&mut str作为的独占指针,则str可以对其进行变异,并且对其进行变异的所有安全函数都将确保UTF-8约束得到维护,因为如果违反该约束,那么我们将具有未定义的行为,因为库假定此约束为正确,不检查。

那么什么是String?这是3个字; 两者与for相同,&str但是增加了第三个词,即str堆上缓冲区的容量,始终在堆中(str必须在堆上)之前管理(在堆上,并且必须重新分配)它管理的缓冲区容量。在String基本拥有一个str像他们说的; 它控制它,可以调整它的大小,并在合适时重新分配它。所以String说a &str比说a更接近a str

另一件事是Box<str>;它也拥有一个,str并且其运行时表示形式与a相同,&str但是它也拥有一个str与众不同的地方,&str但是它无法调整大小,因为它不知道其容量,因此基本上a Box<str>可以看作是String无法调整大小的固定长度(您可以String如果您要调整大小,请务必将其转换为)。

除UTF-8约束外[T]Vec<T>其他之间存在非常相似的关系,并且它可以容纳大小不是动态的任何类型。

str类型类型上的使用主要是使用&str; 它存在于类型级别,以便能够方便地编写特征。从理论上讲str,类型不是必须存在的东西,而仅仅&str是这样,这意味着必须编写许多现在可以通用的额外代码。

&str超级有用,String无需复制即可拥有一个的多个不同子字符串;String 如前所述,a 拥有str它所管理的堆,并且如果您只能String用新的创建子字符串,String则必须将其复制,因为Rust中的所有内容只能由一个所有者来处理内存安全性。因此,例如,您可以切片字符串:

let string: String   = "a string".to_string();
let substring1: &str = &string[1..3];
let substring2: &str = &string[2..4];

我们有str相同字符串的两个不同子字符串。string是拥有str堆上实际完整缓冲区的缓冲区,&str子字符串只是指向堆上该缓冲区的胖指针。


4

std::String只是的向量u8。您可以在源代码中找到其定义。它是堆分配的并且可增长。

#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
    vec: Vec<u8>,
}

str是基本类型,也称为字符串切片。字符串切片的大小固定。文字字符串如let test = "hello world"具有&'static str类型。test是对此静态分配的字符串的引用。 &str无法修改,例如,

let mut word = "hello world";
word[0] = 's';
word.push('\n');

str确实具有可变切片&mut str,例如: pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)

let mut s = "Per Martin-Löf".to_string();
{
    let (first, last) = s.split_at_mut(3);
    first.make_ascii_uppercase();
    assert_eq!("PER", first);
    assert_eq!(" Martin-Löf", last);
}
assert_eq!("PER Martin-Löf", s);

但是对UTF-8的微小更改可以更改其字节长度,并且分片无法重新分配其引用对象。


0

用简单的话来说,String数据类型是存储在堆上的(就像Vec),您可以访问该位置。

&str是切片类型。这意味着它只是引用String堆中已经存在的某个地方。

&str在运行时不进行任何分配。因此,出于内存原因,您可以使用&strover String。但是,请记住,使用&str时可能必须要处理明确的生命周期。


1
堆中的某处 -并不完全准确。
Shepmaster

我的意思是,strview的已经存在String的堆。
00imvj00 '18

1
我理解您的意思,这并不是完全准确。“堆”不是语句的必需部分。
Shepmaster

-1

对于C#和Java用户:

  • 锈' String===StringBuilder
  • Rust的&str ===(不可变)字符串

我喜欢将a &str视为对字符串的视图,就像Java / C#中的固定字符串,您无法更改它,只能创建一个新字符串。


1
Java / C#字符串和Rust字符串之间的最大区别在于,Rust确保该字符串是正确的unicode,因此,要使字符串中的第三个字符需要更多的思考,而不仅仅是“ abc” [2]。(考虑到我们生活在一个多语言的世界中,这是一件好事。)
松鼠

这是不正确的。可变性主题已在投票最多的答案中得到了解决;请阅读以了解更多信息。
Shepmaster

-5

这是一个快速简单的解释。

String-可增长的,可拥有的堆分配数据结构。它可以被强制为&str

str-是(现在,随着Rust的发展)可变的,固定长度的字符串,它驻留在堆或二进制文件中。您只能str通过字符串切片视图作为借用类型进行交互,例如&str

使用注意事项:

身高:String如果你想拥有或突变的字符串-如字符串传递给另一个线程,等等。

身高:&str如果你想有一个字符串的只读视图。


这是不正确的。可变性主题已在投票最多的答案中得到了解决;请阅读以了解更多信息。
Shepmaster
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.