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

Dan*_*ath 341 string rust

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

huo*_*uon 406

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

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

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

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

总之,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允许轻松创建这些类型.

  • "UTF-8字节序列(**长度未知**)" - 这是否过时了?[docs](https://doc.rust-lang.org/nightly/std/primitive.str.html)说"A`&str`由两个组成部分组成:指向某些字节的指针和一个长度. " (8认同)
  • 它没有过时(表示相当稳定),只是有点不精确:它不是静态知道,不像说,[u8; N]`. (7认同)
  • @mrec 在编译时是未知的,不能假设它的大小,例如,在创建堆栈帧时。因此,为什么它经常被视为引用,引用在编译时是已知大小的,也就是指针的大小。 (4认同)
  • @cjohansson静态分配的对象通常既不存储在堆上,也不存储在堆栈上,而是存储在它们自己的内存区域中。 (4认同)
  • @lxx,不,Rust 的所有权和借用开始起作用:编译器不会让你持有一个指向超出范围并被释放的“String”的“&amp;str”切片。在垃圾收集语言中,切片可以在主所有者消失后存在,但在 Rust 中则不能:编译器强制程序员显式选择如何处理它,例如不共享内存(通过使用 .to_owned() 来制作一个单独的“String”),或者像你说的那样共享内存(通过使用类似http://kimundi.github.io/owning-ref-rs/owning_ref/index.html#caching-a-subslice-of-a -细绳 )。 (3认同)

Lui*_*uso 78

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

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

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

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

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

let a: String = "hello rust".into();
Run Code Online (Sandbox Code Playgroud)

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

let a: &str = "hello rust";
Run Code Online (Sandbox Code Playgroud)

  • 像string_view? (8认同)
  • 是的,就像 string_view 一样,但它是语言固有的,并且正确地借用了检查。 (8认同)
  • `string_view` 是令人厌恶的。想象一下 `auto_ptr&lt;T&gt;` 级别很糟糕。 (2认同)

Chr*_*gan 37

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

String过去是~str一个可增长的,拥有UTF-8字节数组.

  • 我也喜欢[你的博客文章](https://chrismorgan.info/blog/rust-fizzbuzz/),“两种类型的字符串?这是什么?” 特别是:“`String` 是一个*拥有*类型...它对字符串内容拥有独占所有权;当它超出范围时,字符串内容的内存将立即被释放。为此原因,任何子字符串都不能是“String”类型...[否则]...当一个子字符串超出范围时,另一个将变得无效...因此,切片(子字符串)使用type 是对其他东西拥有的内容的引用 -`​&amp;str`...” (3认同)
  • 从技术上讲,以前的 `~str` 现在是 `Box&lt;str&gt;` (2认同)
  • @ jv110:不,因为`~str`是可以增长的,而`Box <str>`是不可增长的.(那个`~str`和`〜[T]`神奇地成长,不像其他任何`~` -object,正是为什么引入了`String`和`Vec <T>`,因此规则都很简单,是一致的.) (2认同)

Wil*_*een 37

铁锈&strString


String:

  • Rust拥有的 String 类型,字符串本身存在于堆上,因此是可变的,可以改变它的大小和内容。
  • 因为当拥有该字符串的变量超出范围时,该字符串就被拥有了,因此堆上的内存将被释放。
  • 类型的变量String是胖指针(指针+关联的元数据)
  • 胖指针的长度为 3 * 8 字节(字长),由以下 3 个元素组成:
    • 指向堆上实际数据的指针,它指向第一个字符
    • 字符串长度(字符数)
    • 字符串在堆上的容量

&str:

  • Rust 的非拥有 String 类型,默认情况下是不可变的。字符串本身位于内存中的其他位置,通常位于堆或'static内存中。
  • 由于字符串是非拥有的,因此当&str变量超出范围时,字符串的内存将不会被释放。
  • 类型的变量&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 throughout 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 also change
        println!("{}, {}", string2, string2.capacity()); // defghijk, 8

    } // memory of string2 on the heap will be freed here because owner goes out of scope

}
Run Code Online (Sandbox Code Playgroud)


00i*_*j00 13

简而言之,String数据类型存储在堆上(就像Vec),并且您可以访问该位置。

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

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


  • 我明白这就是你的意思,但我的意思是这并不完全准确。“堆”不是语句的必需部分。 (6认同)
  • *堆中的某个地方*——这并不完全准确。 (4认同)

Zor*_*orf 13

它们实际上完全不同。首先,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];
Run Code Online (Sandbox Code Playgroud)

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

  • “它不能存储在变量中,因为编译器需要在编译时知道每个变量的大小是多少”&gt;您能否解释一下为什么编译器不能生成使用有关字符串长度的运行时信息的二进制代码,请?这是 Rust 设计的限制吗? (2认同)
  • @Mergasov它可以,但是这在性能方面会非常低效,并且会完全改变依赖于已知信息的函数调用约定。函数调用堆栈的大小以及其中每个变量的位置在编译时已知,对于生成高效的程序非常重要,这也是堆栈比堆快几个数量级的原因。在这种情况下,简单地将其放在堆上指针后面要容易得多;它本质上是将堆栈变成第二个堆。 (2认同)

Ape*_*ion 12

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

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

str是一种原始类型,也称为字符串 slice。字符串切片具有固定大小。像这样的文字字符串let test = "hello world"具有&'static str类型。test是对这个静态分配的字符串的引用。 &str不能修改,例如

let mut word = "hello world";
word[0] = 's';
word.push('\n');
Run Code Online (Sandbox Code Playgroud)

str确实有可变 slice &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);
Run Code Online (Sandbox Code Playgroud)

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


snn*_*snn 11

str类似于String,而不是它的切片,也称为&str

Anstr是一个字符串文字,基本上是一个预先分配的文本:

"Hello World"
Run Code Online (Sandbox Code Playgroud)

该文本必须存储在某处,因此它与程序的机器代码一起存储在可执行文件的数据部分中,作为字节序列 ([u8])。因为文本可以有任何长度,它们是动态大小的,它们的大小仅在运行时才知道:

+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
|  H |  e  |  l  |  l  |  o  |    |  W |  o  |  r  |  l  |  d  |
+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+

+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
| 72 | 101 | 108 | 108 | 111 | 32 | 87 | 111 | 114 | 108 | 100 |
+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
Run Code Online (Sandbox Code Playgroud)

我们需要访问存储的文本,这就是切片的用武之地。

切片[T]是一个视图到存储器块。无论是否可变,切片总是借用,这就是为什么它总是在指针,后面&

因此,“Hello World”表达式返回一个胖指针,包含实际数据的地址及其长度。这个指针将是我们实际数据的句柄。现在数据在指针后面,编译器在编译时知道它的大小。

由于文本存储在源代码中,它将在运行程序的整个生命周期内有效,因此将具有static生命周期。

所以,"Hello Word" 表达式的返回值应该反映这两个特征,它所做的:

+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
|  H |  e  |  l  |  l  |  o  |    |  W |  o  |  r  |  l  |  d  |
+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+

+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
| 72 | 101 | 108 | 108 | 111 | 32 | 87 | 111 | 114 | 108 | 100 |
+----+-----+-----+-----+-----+----+----+-----+-----+-----+-----+
Run Code Online (Sandbox Code Playgroud)

你可能会问为什么它的类型写为 asstr而不是 as [u8],这是因为数据总是保证是有效的 UTF-8 序列。并非所有 UTF-8 字符都是单字节,有些是 4 个字节,并且并非所有字节序列都是有效的 UTF-8 字符。所以 [u8] 是不准确的。

另一方面,String是 u8 字节的专用向量,换句话说,可调整大小的缓冲区保存 UTF-8 文本。我们说专业是因为它不允许任意访问并强制执行某些检查数据始终是有效的 UTF-8。缓冲区在堆上分配,因此它可以根据需要或请求调整其缓冲区的大小。

以下是它在源代码中的定义:

let s: &'static str = "Hello World";
Run Code Online (Sandbox Code Playgroud)

您将能够使用Stringstruct创建字符串,但它vec是私有的以确保有效性和正确检查,因为并非所有字节流都是有效的 utf-8 字符。

但是在 String 类型上定义了几种方法来创建 String 实例,new 是其中之一:

pub struct String {
    vec: Vec<u8>,
}
Run Code Online (Sandbox Code Playgroud)

我们可以用它来创建一个有效的字符串。不幸的是它不接受输入参数。所以结果将是有效的,但一个空字符串:

pub const fn new() -> String {
  String { vec: Vec::new() }
}
Run Code Online (Sandbox Code Playgroud)

但是我们可以用来自不同来源的初始值填充这个缓冲区:

从字符串文字

let s = String::new();
println("{}", s);
Run Code Online (Sandbox Code Playgroud)

从原材料

let a = "Hello World";
let s = String::from(a);
Run Code Online (Sandbox Code Playgroud)

从一个字符

let ptr = s.as_mut_ptr();
let len = s.len();
let capacity = s.capacity();

let s = String::from_raw_parts(ptr, len, capacity);
Run Code Online (Sandbox Code Playgroud)

从字节向量

let ch = 'c';
let s = ch.to_string();
Run Code Online (Sandbox Code Playgroud)

从输入缓冲区

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
Run Code Online (Sandbox Code Playgroud)

或者来自任何其他实现ToStringtrait 的类型

由于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(())
}
Run Code Online (Sandbox Code Playgroud)

大多数示例都使用String::from,因此人们在思考为什么要从另一个字符串创建 String 时感到困惑。

长文了,希望能帮到你。


Yil*_*maz 10

\n

在这3种不同类型中

\n
let noodles = "noodles".to_string();\nlet oodles = &noodles[1..];\nlet poodles = "\xe0\xb2\xa0_\xe0\xb2\xa0"; // this is string literal\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • 字符串有一个可调整大小的缓冲区,用于保存 UTF-8 文本。缓冲区在堆上分配,因此可以根据需要或\n请求调整其缓冲区大小。在示例中,“noodles”是一个拥有近字节缓冲区的字符串,其中七个正在使用中。您可以将字符串视为保证保存格式正确的 UTF-8 的 Vec;事实上,这就是String实现方式。

    \n
  • \n
  • A&str是对其他人拥有的一系列 UTF-8 文本的引用:它 \xe2\x80\x9cborrows\xe2\x80\x9d 文本。在示例中,oodles 是一个 &str\n,指的是属于“noodles”的文本的最后六个字节,因此\nit 表示文本 \xe2\x80\x9coodles。\xe2\x80\x9d 与其他切片引用一样,&str\ nis a fat pointer,包含实际数据的地址和长度。您可以将 a 视为&str只不过是保证保存格式良好的 UTF-8 的 \n&[u8] 。

    \n
  • \n
  • Astring literal&str指预分配的文本,通常与程序\xe2\x80\x99s 机器\n代码一起存储在只读存储器中。在前面的示例中,poodles 是一个字符串文字,\n指向程序开始执行时创建的七个字节,并持续到程序退出为止。

    \n
  • \n
\n

这就是它们在内存中的存储方式

\n
\n

在此输入图像描述

\n

参考:《Rust 编程》,作者:Jim Blandy、Jason Orendorff、Leonora F。S·廷德尔

\n


kn3*_*n3l 9

一些用法

示例_1.rs

fn main(){
  let hello = String::("hello");
  let any_char = hello[0];//error
}
Run Code Online (Sandbox Code Playgroud)

示例_2.rs

fn main(){
  let hello = String::("hello");
  for c in hello.chars() {
    println!("{}",c);
  }
}
Run Code Online (Sandbox Code Playgroud)

示例_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);
}
Run Code Online (Sandbox Code Playgroud)

Shadowing

fn main() {
  let s: &str = "hello"; // &str
  let s: String = s.to_uppercase(); // String
  println!("{}", s) // HELLO
}
Run Code Online (Sandbox Code Playgroud)

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
 }
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,String&str是不同的类型,并且在 99% 的情况下,您只应该关心&str


Evg*_*ene 7

String是一个对象。

&str是指向对象一部分的指针。

在此输入图像描述


Squ*_*rel 6

对于 C# 和 Java 人员:

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

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

  • Java/C# 字符串和 Rust 字符串之间最大的区别在于,Rust 保证字符串是正确的 unicode,因此获取字符串中的第三个字符需要更多的考虑,而不仅仅是“abc”[2]。(鉴于我们生活在一个多语言的世界,这是一件好事。) (3认同)
  • [这是不正确的](https://doc.rust-lang.org/std/string/struct.String.html#method.as_mut_str)。投票最高的答案中已经讨论了可变性的主题;请阅读它以了解更多信息。 (3认同)
  • &amp;mut str 非常不常见,并且与 &amp;str 不同。 (2认同)

小智 5

在 Rust 中,str 是一种原始类型,表示 Unicode 标量值序列,也称为字符串切片。这意味着它是字符串的只读视图,并且它不拥有它指向的内存。另一方面,String 是一种可增长、可变、拥有的字符串类型。这意味着当您创建一个 String 时,它将在堆上分配内存来存储字符串的内容,并且当 String 超出范围时它将释放该内存。由于字符串是可增长且可变的,因此您可以在创建字符串后更改其内容。

一般来说,当您想要引用存储在另一个数据结构(例如字符串)中的字符串切片时,请使用 str。当您想要创建并拥有字符串值时,可以使用字符串。