为什么Rust有String
和str
? String
和str
之间有什么区别?什么时候使用String
代替str
,反之亦然?其中一个被弃用了吗?
答案 0 :(得分:335)
String
是动态堆字符串类型,如Vec
:当您需要拥有或修改字符串数据时使用它。
str
是一个在内存中某处具有动态长度的UTF-8字节的不可变 1 序列。由于大小未知,因此只能在指针后面处理它。这意味着str
最常见的 2 显示为&str
:对某些UTF-8数据的引用,通常称为"字符串切片"或只是一个"切片"。 A slice只是对某些数据的看法,而且数据可以在任何地方,例如。
"foo"
是&'static str
。数据被硬编码到可执行文件中,并在程序运行时加载到内存中。String
的String
数据的String
dereferences to a &str
view内堆中。 在堆栈:例如下面创建一个堆栈分配的字节数组,然后得到一个view of that data as a &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 str
是固定长度的;你不能写超出结尾的字节,或留下尾随无效字节。由于UTF-8是一种可变宽度编码,因此在很多情况下这有效地强制所有str
都是不可变的。通常,变异需要写入比以前更多或更少的字节(例如,用a
(2 +字节)替换ä
(1个字节)将需要在{{1}中腾出更多空间})。有一些特定方法可以修改str
,主要是那些只处理ASCII字符的方法,如make_ascii_uppercase
。
2 Dynamically sized types允许&str
之类的内容,因为Rust 1.2之后的引用序列计数了UTF-8字节。 Rust 1.21允许轻松创建这些类型。
答案 1 :(得分:54)
我有C ++背景,我发现在C ++术语中考虑String
和&str
非常有用:
String
就像std::string
;它拥有内存并完成管理内存的肮脏工作。&str
就像char*
(但有点复杂);它将我们指向一个块的开头,就像你可以获得指向std::string
内容的指针一样。他们中的任何一个会消失吗?我不这么认为。它们有两个目的:
String
保留缓冲区并且非常实用。 &str
是轻量级的,应该用于&#34;外观&#34;变成字符串。您可以搜索,拆分,解析甚至替换块,而无需分配新内存。
&str
可以查看String
内部,因为它可以指向某个字符串文字。以下代码需要将文字字符串复制到String
托管内存中:
let a: String = "hello rust".into();
以下代码允许您使用文字本身而不使用副本(只读)
let a: &str = "hello rust";
答案 2 :(得分:36)
答案 3 :(得分:5)
它们实际上完全不同。首先,str
只是类型级别的东西;它只能在类型级别进行推理,因为它是所谓的动态大小类型(DST)。 str
占用的大小在编译时无法知道,并且取决于运行时信息-无法将其存储在变量中,因为编译器需要在编译时知道每个变量的大小。 str
从概念上讲只是u8
个字节的行,并保证它形成有效的UTF-8。行有多大?在运行时之前没人知道,因此无法将其存储在变量中。
有趣的是,在运行时确实存在&str
或指向str
的任何其他指针Box<str>
。这就是所谓的“胖指针”;它是一个具有额外信息的指针(在这种情况下,它是指向对象的大小),因此它的大小是其两倍。实际上,&str
非常接近String
(但不接近&String
)。 &str
是两个词;一个指针指向str
的第一个字节,另一个指针描述str
的字节数。
与所说的相反,str
不需要是不变的。如果您可以将&mut str
作为指向str
的排他指针,则可以对其进行变异,并且对其进行变异的所有安全函数都可以确保UTF-8约束得到保留,因为如果违反了该约束,那么我们具有未定义的行为,因为该库假定此约束为true并且不对其进行检查。
那么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
在其管理的堆上{em>拥有 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 :(得分:3)
str
与String
类似,而不是切片,也称为&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,&
之后。
因此,“ Hello World”表达式将返回一个胖指针,其中包含实际数据的地址及其长度。该指针将是我们处理实际数据的句柄。现在数据位于指针后面,编译器在编译时便知道其大小。
由于文本存储在源代码中,因此它将在正在运行的程序的整个生命周期内有效,因此将具有static
生命周期。
因此,“ Hello Word”表达式的返回值应反映出这两个特征,
let s: &'static str = "Hello World";
您可能会问为什么将其类型写为str
而不写为[u8]
,这是因为始终保证数据是有效的UTF-8序列。并非所有UTF-8字符都是单字节,有些不是4字节,也不是所有字节序列都是有效的UTF-8字符。因此[u8]是不准确的。
另一方面,String
是u8字节的专用向量,换句话说,可调整大小的缓冲区保存UTF-8文本。我们之所以说专用是因为它不允许任意访问,并强制执行某些检查以确保数据始终是有效的UTF-8。缓冲区是在堆上分配的,因此可以根据需要或请求调整缓冲区的大小。
以下是在源代码中的定义:
pub struct String {
vec: Vec<u8>,
}
您可以使用String
结构创建字符串,但是vec
是私有的,以确保有效性和正确的检查,因为并非所有字节流都是有效的utf-8字符。
但是在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);
来自原始零件
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
来自输入缓冲区
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感到困惑。
这是一本长篇小说,希望对您有所帮助。
答案 5 :(得分:3)
&str
和String
String
:
String
类型的变量是胖指针(指针 + 关联元数据)&str
:
'static
内存中。&str
变量超出范围时,String 是非拥有的,所以字符串的内存不会被释放。&str
类型的变量是胖指针(指针 + 关联元数据)示例:
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
}
答案 6 :(得分:0)
简单来说,String
是存储在堆上的数据类型(就像Vec
),并且您可以访问该位置。
&str
是切片类型。这意味着它只是引用堆中某处已存在的String
。
&str
在运行时没有进行任何分配。因此,出于内存原因,您可以使用&str
而不是String
。但是,请记住,使用&str
时,您可能必须处理明确的生命周期。
答案 7 :(得分:0)
std::String
只是u8
的向量。您可以在source code 中找到其定义。它是堆分配的并且可增长。
#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
vec: Vec<u8>,
}
str
是一种原始类型,也称为 string slice 。字符串切片的大小固定。像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的微小更改可以更改其字节长度,并且分片无法重新分配其引用对象。
答案 8 :(得分:0)
对于C#和Java用户:
String
=== StringBuilder
&str
===(不可变)字符串我喜欢将&str
看作是字符串的视图,就像Java / C#中的固定字符串,您不能更改它,只能创建一个新字符串。
答案 9 :(得分:0)
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
。
答案 10 :(得分:-2)
这里是一个快速简单的解释。
String
-可增长的,可拥有的堆分配数据结构。可以将其强制为&str
。
str
-是(现在,随着Rust的发展)可变的,固定长度的字符串,该字符串存在于堆或二进制文件中。您只能通过诸如str
之类的字符串切片视图与&str
作为借用类型进行交互。
使用注意事项:
如果您想拥有或更改一个字符串(例如,将该字符串传递给另一个线程等),请首选String
。
如果要对字符串进行只读查看,请首选&str
。