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

问题描述 投票:271回答:8

为什么Rust有StringstrStringstr有什么区别?什么时候使用String而不是str,反之亦然?其中一个被弃用了吗?

string rust
8个回答
345
投票

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

str是一个在内存中某处具有动态长度的UTF-8字节的immutable1序列。由于大小未知,因此只能在指针后面处理它。这意味着str最常见的2表现为&str:对某些UTF-8数据的引用,通常称为“字符串切片”或仅仅是“切片”。 A slice只是对某些数据的看法,而且数据可以在任何地方,例如

  • 在静态存储中:字符串文字"foo"&'static str。数据被硬编码到可执行文件中,并在程序运行时加载到内存中。
  • 在堆里面分配StringString dereferences to a &str viewString数据。
  • 在堆栈上:例如下面创建一个堆栈分配的字节数组,然后得到一个view of that data as a &struse 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是一种可变宽度编码,因此在许多情况下,这有效地强制所有strs都是不可变的。通常,突变需要写入比以前更多或更少的字节(例如,用a(2+字节)替换ä(1字节)将需要在str中腾出更多空间)。有一些特定的方法可以修改&str,主要是那些只处理ASCII字符的方法,比如make_ascii_uppercase

2 Dynamically sized types允许像Rc<str>这样的东西用于引用序列,自Rust 1.2以来计算了UTF-8字节。 Rust 1.21允许轻松创建这些类型。


56
投票

我有一个C ++背景,我发现用C ++术语来思考String&str是非常有用的:

  • Rust String就像一个std::string;它拥有内存并完成管理内存的肮脏工作。
  • Rust &str就像一个char*(但更复杂一点);它将我们指向一个块的开头,就像你可以获得指向std::string内容的指针一样。

它们中的任何一个都会消失吗?我不这么认为。它们有两个目的:

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

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

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

以下代码允许您使用文本本身而不复制(尽管只读)

let a: &str = "hello rust";

36
投票

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

String曾经是~str,一个可增长的,拥有UTF-8字节数组。


5
投票

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

有趣的是,&str或任何其他指向str的指针,如Box<str>确实存在于运行时。这是一个所谓的“胖指针”;它是一个带有额外信息的指针(在这种情况下是它所指向的东西的大小)所以它是两倍大。事实上,&str非常接近String(但不是&String)。一个&str是两个字;一个指向str第一个字节的指针和另一个描述str长度为多少字节的数字。

与所谓的相反,str不需要是不可变的。如果你可以得到一个&mut str作为str的独占指针,你可以改变它,所有改变它的安全函数保证UTF-8约束得到维护,因为如果违反了那么我们有未定义的行为,因为库假设这个约束为真,不检查它。

什么是String?这是三个字;两个与&str相同,但它增加了第三个字,即堆上的str缓冲区的容量,总是在堆上(str不一定在堆上)它在填充之前管理并且必须重新分配。 String基本上拥有他们所说的str;它控制它并可以调整它并在它认为合适时重新分配它。所以String更接近&str而不是str

另一件事是Box<str>;这也拥有一个str和它的运行时表示与&str相同,但它也拥有str不像&str但它不能调整它,因为它不知道它的容量所以基本上一个Box<str>可以看作固定长度String无法调整大小(如果要调整大小,可以随时将其转换为String)。

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

在类型级别上使用str主要是为了创建&str的通用抽象;它存在于类型级别,以便能够方便地写出特征。理论上str作为一种类型的东西不需要存在,只需要&str,但这意味着必须编写许多额外的代码,现在可以是通用的。

&str非常有用,能够拥有String的多个不同子串而无需复制;如上所述,String在它管理的堆上拥有str,如果你只能用新的String创建String的子串,则必须复制,因为Rust中的所有东西只能有一个所有者来处理内存安全。例如,您可以切片字符串:

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

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


0
投票

简单来说,qazxsw poi是存储在堆上的数据类型(就像qazxsw poi一样),并且您可以访问该位置。

String是一种切片类型。这意味着它只是引用堆中某处已有的Vec

&str在运行时不进行任何分配。所以,出于记忆的原因,你可以使用String而不是&str。但是,请记住,使用&str时,您可能需要处理显式生命周期。


0
投票

String只是&str的矢量。你可以在std::String找到它的定义。它是堆分配和可扩展的。

u8

source code 是一种原始类型,也称为字符串切片。字符串切片具有固定大小。像#[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

但是对UTF-8的一个小改动可以改变它的字节长度,并且切片不能重新分配它的指示物。


0
投票

对于C#和Java人员:

  • Rust'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);
  • Rust的String ===(不可变)字符串

我喜欢将StringBuilder视为字符串的视图,就像Java / C#中的实际字符串一样,您无法更改它,只创建一个新字符串。


-2
投票

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

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

String - (现在,正如Rust演化的那样)可变的,固定长度的字符串,它存在于堆或二进制文件中。您只能通过字符串切片视图与&str作为借用类型进行交互,例如str

使用注意事项

如果你想拥有或改变一个字符串,比如将字符串传递给另一个线程等,请更喜欢str

如果你想拥有一个字符串的只读视图,请更喜欢&str

© www.soinside.com 2019 - 2024. All rights reserved.