String
是动态堆字符串类型,如 Vec
:当您需要拥有或修改您的字符串数据时使用它。
str
是内存中某处动态长度的 UTF-8 字节的不可变1 序列。由于大小未知,因此只能在指针后面处理。这意味着 str
通常2 显示为 &str
:对某些 UTF-8 数据的引用,通常称为“字符串切片”或仅称为“切片”。 A slice 只是一些数据的视图,这些数据可以在任何地方,例如
在静态存储中:字符串文字“foo”是 &'static str。数据被硬编码到可执行文件中,并在程序运行时加载到内存中。
在堆分配的字符串内:字符串取消引用到字符串数据的 &str 视图。
在堆栈上:例如,以下创建一个堆栈分配的字节数组,然后以 &str 的形式获取该数据的视图:使用 std::str;让 x: &[u8] = &[b'a', b'b', b'c'];让 stack_str: &str = str::from_utf8(x).unwrap();
总之,如果您需要拥有的字符串数据(例如将字符串传递给其他线程,或在运行时构建它们),请使用 String
,如果您只需要查看字符串,请使用 &str
。
这与向量 Vec<T>
和切片 &[T]
之间的关系相同,并且类似于一般类型的按值 T
和按引用 &T
之间的关系。
1 str
是固定长度的;您不能写入超出结尾的字节,或留下尾随无效字节。由于 UTF-8 是一种可变宽度编码,因此在许多情况下,这有效地强制所有 str
不可变。一般来说,突变需要比以前写入更多或更少的字节(例如,用 ä
(2+ 字节)替换 a
(1 字节)将需要在 str
中腾出更多空间)。有一些特定的方法可以就地修改 &mut str
,主要是那些只处理 ASCII 字符的方法,例如 make_ascii_uppercase
。
2 Dynamically sized types 从 Rust 1.2 开始允许使用 Rc<str>
之类的引用计数 UTF-8 字节序列。 Rust 1.21 允许轻松创建这些类型。
我有 C++ 背景,我发现用 C++ 术语思考 String
和 &str
非常有用:
Rust 字符串就像一个 std::string;它拥有内存并执行管理内存的脏活。
Rust &str 类似于 char*(但更复杂一点);它以与您可以获取指向 std::string 内容的指针相同的方式将我们指向块的开头。
他们中的任何一个都会消失吗?我不这么认为。它们有两个目的:
String
保留缓冲区,使用起来非常实用。 &str
是轻量级的,应该用于“查看”字符串。您可以搜索、拆分、解析甚至替换块,而无需分配新内存。
&str
可以查看 String
的内部,因为它可以指向某个字符串文字。以下代码需要将文字字符串复制到 String
托管内存中:
let a: String = "hello rust".into();
以下代码允许您使用文字本身而无需复制(但只读)
let a: &str = "hello rust";
与 String
类似的是 str
,而不是它的切片,也称为 &str
。
str
是字符串文字,基本上是预先分配的文本:
"Hello World"
该文本必须存储在某个地方,因此它与程序的机器代码一起存储在可执行文件的数据部分中,作为字节序列([u8])。因为文本可以是任意长度,它们是动态大小的,它们的大小只有在运行时才知道:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ H │ e │ l │ l │ o │ │ W │ o │ r │ l │ d │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ 72 │ 101 │ 108 │ 108 │ 111 │ 32 │ 87 │ 111 │ 114 │ 108 │ 100 │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
我们需要一种访问存储文本的方法,这就是切片的用武之地。
slice、[T]
是内存块的视图。无论是否可变,切片总是借用,这就是为什么它总是在 pointer、&
后面。
让我们解释动态调整大小的含义。一些编程语言,如 C,在其字符串的末尾附加一个零字节 (\0
) 并记录起始地址。要确定字符串的长度,程序必须从起始位置遍历原始字节,直到找到这个零字节。因此,文本的长度可以是任意大小,因此它是动态大小的。
然而,Rust 采用了不同的方法:它使用切片。切片存储 str
开始的地址以及它需要多少字节。它比附加零字节要好,因为在编译期间会提前完成计算。
因此,“Hello World”表达式返回一个胖指针,包含实际数据的地址及其长度。这个指针将是我们对实际数据的句柄,它也将存储在我们的程序中。现在数据在指针后面,编译器在编译时就知道它的大小。
由于文本存储在源代码中,它将在运行程序的整个生命周期内有效,因此将具有 static
生命周期。
所以,“Hello Word”表达式的返回值应该反映这两个特征,它确实:
let s: &'static str = "Hello World";
你可能会问为什么它的类型写成 str
而不是 [u8]
,这是因为数据总是保证是一个有效的 UTF-8 序列。并非所有 UTF-8 字符都是单字节,有些需要 4 个字节。所以 [u8] 是不准确的。
如果您反汇编已编译的 Rust 程序并检查可执行文件,您将看到多个 str
彼此相邻存储在数据部分中,而没有任何指示一个开始和另一个结束的位置。
编译器更进一步。如果在程序的多个位置使用相同的静态文本,Rust 编译器将优化您的程序并在可执行文件的数据部分创建一个二进制块,并且代码中的每个切片都指向这个二进制块。
例如,即使我们在 "Hello World"
中使用了三种不同的文字,编译器也会为以下代码创建一个内容为“Hello World”的连续二进制文件:
let x: &'static str = "Hello World";
let y: &'static str = "Hello World";
let z: &'static str = "Hello World";
另一方面,String
是一种特殊类型,将其值存储为 u8 的向量。以下是在源代码中定义 String
类型的方式:
pub struct String {
vec: Vec<u8>,
}
作为向量意味着它像任何其他向量值一样被堆分配和调整大小。
专业化意味着它不允许任意访问并强制执行某些检查以确保数据始终是有效的 UTF-8。除此之外,它只是一个向量。
因此, String
是一个可调整大小的缓冲区,用于保存 UTF-8 文本。这个缓冲区是在堆上分配的,所以它可以根据需要或请求增长。我们可以以任何我们认为合适的方式填充这个缓冲区。我们可以改变它的内容。
如果你仔细看,vec
字段保持私有以强制执行有效性。由于它是私有的,我们不能直接创建 String 实例。它之所以保持私有,是因为并非所有字节流都会产生有效的 utf-8 字符,并且与底层字节的直接交互可能会破坏字符串。我们通过方法创建 u8
个字节,并且方法运行某些检查。我们可以说,私有化并通过方法进行受控交互提供了一定的保证。
在 String 类型上定义了几种方法来创建 String 实例,new 就是其中之一:
pub const fn new() -> String {
String { vec: Vec::new() }
}
我们可以使用它来创建一个有效的字符串。
let s = String::new();
println("{}", s);
不幸的是它不接受输入参数。因此结果将是有效的,但是是一个空字符串,但是当容量不足以容纳分配的值时,它会像任何其他向量一样增长。但是应用程序性能会受到影响,因为增长需要重新分配。
我们可以用来自不同来源的初始值填充底层向量:
从字符串文字
let a = "Hello World";
let s = String::from(a);
请注意,仍然会创建 str
,其内容会通过 String.from
复制到堆分配的向量中。如果我们检查可执行二进制文件,我们将在数据部分看到内容为“Hello World”的原始字节。这是一些人错过的非常重要的细节。
从原始零件
let ptr = s.as_mut_ptr();
let len = s.len();
let capacity = s.capacity();
let s = String::from_raw_parts(ptr, len, capacity);
从一个字符
let ch = 'c';
let s = ch.to_string();
从字节向量
let hello_world = vec![72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100];
// We know it is valid sequence, so we can use unwrap
let hello_world = String::from_utf8(hello_world).unwrap();
println!("{}", hello_world); // Hello World
这里我们有另一个重要的细节。向量可能有任何值,但不能保证其内容是有效的 UTF-8,因此 Rust 迫使我们通过返回 Result<String, FromUtf8Error>
而不是 String
来考虑这一点。
从输入缓冲区
use std::io::{self, Read};
fn main() -> io::Result<()> {
let mut buffer = String::new();
let stdin = io::stdin();
let mut handle = stdin.lock();
handle.read_to_string(&mut buffer)?;
Ok(())
}
或来自任何其他实现 ToString
特征的类型
由于 String
在底层是一个向量,它会表现出一些向量特征:
指针:指针指向存储数据的内部缓冲区。
长度:长度是当前存储在缓冲区中的字节数。
容量:容量是缓冲区的大小,以字节为单位。因此,长度将始终小于或等于容量。
并将一些属性和方法委托给向量:
pub fn capacity(&self) -> usize {
self.vec.capacity()
}
大多数示例都使用 String::from
,因此人们对为什么要从另一个字符串创建 String 感到困惑。
读了很久,希望对你有帮助。
它们实际上是完全不同的。首先,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
。 string
是拥有堆上实际完整 str
缓冲区的那个,而 &str
子字符串只是指向堆上该缓冲区的胖指针。
Rust &str 和字符串
String
:
Rust 拥有 String 类型,字符串本身存在于堆上,因此是可变的,可以改变其大小和内容。
因为当拥有字符串的变量超出范围时,String 被拥有,所以堆上的内存将被释放。
String 类型的变量是胖指针(指针 + 相关元数据)
胖指针是 3 * 8 字节(字大小)长,由以下 3 个元素组成: 指向堆上实际数据的指针,它指向第一个字符 字符串长度(字符数) 堆上字符串的容量
指向堆上实际数据的指针,它指向第一个字符
字符串的长度(字符数)
堆上字符串的容量
&str
:
Rust 非拥有的 String 类型,默认情况下是不可变的。字符串本身通常位于内存中的其他位置,通常位于堆或“静态内存”中。
因为当 &str 变量超出范围时 String 是非拥有的,所以不会释放字符串的内存。
&str 类型的变量是胖指针(指针 + 相关元数据)
胖指针为 2 * 8 字节(字大小)长,由以下 2 个元素组成: 指向堆上实际数据的指针,它指向第一个字符 字符串的长度(字符数)
指向堆上实际数据的指针,它指向第一个字符
字符串的长度(字符数)
例子:
use std::mem;
fn main() {
// on 64 bit architecture:
println!("{}", mem::size_of::<&str>()); // 16
println!("{}", mem::size_of::<String>()); // 24
let string1: &'static str = "abc";
// string will point to `static memory which lives through the whole program
let ptr = string1.as_ptr();
let len = string1.len();
println!("{}, {}", unsafe { *ptr as char }, len); // a, 3
// len is 3 characters long so 3
// pointer to the first character points to letter a
{
let mut string2: String = "def".to_string();
let ptr = string2.as_ptr();
let len = string2.len();
let capacity = string2.capacity();
println!("{}, {}, {}", unsafe { *ptr as char }, len, capacity); // d, 3, 3
// pointer to the first character points to letter d
// len is 3 characters long so 3
// string has now 3 bytes of space on the heap
string2.push_str("ghijk"); // we can mutate String type, capacity and length will aslo change
println!("{}, {}", string2, string2.capacity()); // defghijk, 8
} // memory of string2 on the heap will be freed here because owner goes out of scope
}
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
,例如: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 的一个小改动可以改变它的字节长度,并且一个 slice 不能重新分配它的引用对象。
String
的 &mut str
,也就是说,没有 to_string()
,因为如果您已经有 String,为什么还要打扰 str。这有效:let mut s: Box<str> = "Per Martin-Löf".into(); let (first, last) = s.split_at_mut(3); first.make_ascii_uppercase(); assert_eq!("PER Martin-Löf", &*s);
简单来说,String
是存储在堆上的数据类型(就像 Vec
),您可以访问该位置。
&str
是切片类型。这意味着它只是对堆中某处已经存在的 String
的引用。
&str
在运行时不进行任何分配。因此,出于记忆原因,您可以使用 &str
而不是 String
。但是,请记住,在使用 &str
时,您可能必须处理显式生命周期。
str
是堆中已经存在的 String
的 view
。
一些用法
example_1.rs
fn main(){
let hello = String::("hello");
let any_char = hello[0];//error
}
example_2.rs
fn main(){
let hello = String::("hello");
for c in hello.chars() {
println!("{}",c);
}
}
example_3.rs
fn main(){
let hello = String::("String are cool");
let any_char = &hello[5..6]; // = let any_char: &str = &hello[5..6];
println!("{:?}",any_char);
}
Shadowing
fn main() {
let s: &str = "hello"; // &str
let s: String = s.to_uppercase(); // String
println!("{}", s) // HELLO
}
function
fn say_hello(to_whom: &str) { //type coercion
println!("Hey {}!", to_whom)
}
fn main(){
let string_slice: &'static str = "you";
let string: String = string_slice.into(); // &str => String
say_hello(string_slice);
say_hello(&string);// &String
}
Concat
// String is at heap, and can be increase or decrease in its size
// The size of &str is fixed.
fn main(){
let a = "Foo";
let b = "Bar";
let c = a + b; //error
// let c = a.to_string + b;
}
请注意,String
和 &str
是不同的类型,在 99% 的情况下,您只需关心 &str
。
对于 C# 和 Java 人员:
Rust' String === StringBuilder
Rust 的 &str === (不可变) 字符串
我喜欢将 &str
视为字符串的视图,就像 Java / C# 中的一个内部字符串,您无法更改它,只能创建一个新字符串。
这是一个快速简单的解释。
String
- 一种可增长的、可拥有的堆分配数据结构。它可以被强制为 &str
。
str
- (现在,随着 Rust 的发展)是可变的、固定长度的字符串,存在于堆或二进制文件中。您只能通过字符串切片视图(例如 &str
)作为借用类型与 str
进行交互。
使用注意事项:
如果您想拥有或改变一个字符串,请首选 String
- 例如将字符串传递给另一个线程等。
如果您想获得字符串的只读视图,请首选 &str
。
不定期副业成功案例分享
&str
由两部分组成:指向某些字节的指针和长度。”[u8; N]
。String
的&str
切片。在垃圾回收语言中,切片可以在主所有者消失后存在,但在 Rust 中则不能:编译器强制程序员明确选择如何处理它,例如不共享内存(通过使用.to_owned()
String
),或者像你说的那样共享内存(使用类似 kimundi.github.io/owning-ref-rs/owning_ref/… 的东西)。