为什么Rust有String
和str
? String
和str
有什么区别?什么时候使用String
而不是str
,反之亦然?其中一个被弃用了吗?
String
是动态堆字符串类型,如Vec
:当您需要拥有或修改字符串数据时使用它。
str
是一个在内存中某处具有动态长度的UTF-8字节的immutable1序列。由于大小未知,因此只能在指针后面处理它。这意味着str
最常见的2表现为&str
:对某些UTF-8数据的引用,通常称为“字符串切片”或仅仅是“切片”。 A slice只是对某些数据的看法,而且数据可以在任何地方,例如
"foo"
是&'static str
。数据被硬编码到可执行文件中,并在程序运行时加载到内存中。String
:String
dereferences to a &str
view的String
数据。&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都是不可变的。通常,突变需要写入比以前更多或更少的字节(例如,用a
(2+字节)替换ä
(1字节)将需要在str
中腾出更多空间)。有一些特定的方法可以修改&str
,主要是那些只处理ASCII字符的方法,比如make_ascii_uppercase
。
2 Dynamically sized types允许像Rc<str>
这样的东西用于引用序列,自Rust 1.2以来计算了UTF-8字节。 Rust 1.21允许轻松创建这些类型。
我有一个C ++背景,我发现用C ++术语来思考String
和&str
是非常有用的:
String
就像一个std::string
;它拥有内存并完成管理内存的肮脏工作。&str
就像一个char*
(但更复杂一点);它将我们指向一个块的开头,就像你可以获得指向std::string
内容的指针一样。它们中的任何一个都会消失吗?我不这么认为。它们有两个目的:
String
保留缓冲区并且非常实用。 &str
是轻量级的,应该用来“看”成字符串。您可以搜索,拆分,解析甚至替换块,而无需分配新内存。
&str
可以查看String
内部,因为它可以指向一些字符串文字。以下代码需要将文字字符串复制到String
托管内存中:
let a: String = "hello rust".into();
以下代码允许您使用文本本身而不复制(尽管只读)
let a: &str = "hello rust";
它们实际上完全不同。首先,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];
我们有两个不同的子串str
s相同的字符串。 string
是拥有堆上实际的完整str
缓冲区的那个,而&str
子串只是堆上该缓冲区的胖指针。
简单来说,qazxsw poi是存储在堆上的数据类型(就像qazxsw poi一样),并且您可以访问该位置。
String
是一种切片类型。这意味着它只是引用堆中某处已有的Vec
。
&str
在运行时不进行任何分配。所以,出于记忆的原因,你可以使用String
而不是&str
。但是,请记住,使用&str
时,您可能需要处理显式生命周期。
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的一个小改动可以改变它的字节长度,并且切片不能重新分配它的指示物。
对于C#和Java人员:
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);
String
===(不可变)字符串我喜欢将StringBuilder
视为字符串的视图,就像Java / C#中的实际字符串一样,您无法更改它,只创建一个新字符串。
这是一个快速简单的解释。
&str
- 可扩展,可拥有的堆分配数据结构。它可以被强制为&str
。
String
- (现在,正如Rust演化的那样)可变的,固定长度的字符串,它存在于堆或二进制文件中。您只能通过字符串切片视图与&str
作为借用类型进行交互,例如str
。
使用注意事项
如果你想拥有或改变一个字符串,比如将字符串传递给另一个线程等,请更喜欢str
。
如果你想拥有一个字符串的只读视图,请更喜欢&str
。