目录
前言
一、String的理解
二、什么是&str
三、String和&str
四、String 还是 &str ?,该如何选择?
前言
当你开始学习 Rust 编程时,你可能经常遇到字符串使用问题的编译错误,经常搞不懂String和&str什么时候使用,出现了又如何解决。
Rust 的字符串设计与大多数具有单一字符串类型的编程语言有所不同。Unicode 安全的字符串设计和独特的所有权机制都使 Rust 中的字符串对于初学者来说在某种程度上是违反直觉的。
这篇文章我们简单说一下String和&str是什么意思,代表什么含义,尤其是在什么时候使用哪一个类型。
理解这篇文章中涉及的概念将使您能够以更有效的方式使用 Rust 中的字符串,同时也可以更好地理解其他人的代码,因为您将更好地了解为什么做出有关字符串处理的某些决定的原因。
一、String的理解
String 是一个需要动态分配大小的,其大小在代码编译时是未知的,内部的容量是可以随时根据需要变化的。
从String的源码可以看到他的结构如下:
#[derive(PartialOrd, Eq, Ord)]
#[cfg_attr(not(test), rustc_diagnostic_item = "String")]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct String {
vec: Vec<u8>,
}
我们可以通过多种方式创建一个String:
let s = String::new() // An empty String
let s = "Hello".to_string();
let s = String::from("world");
let s: String = "also this".into();
Rust 的字符和字符串类型是围绕 Unicode 设计的。 String不是 ASCII 字符序列,而是 Unicode 字符序列。Rust 类型是一个包含 Unicode 代码的 32 位值。 String使用 UTF-8 将 Unicode 字符编码转为vec。
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
字符串中的.len()
方法将返回以字节为单位的长度,而不是以字符为单位的长度。
assert_eq!("ಠ_ಠ".len(), 7);
assert_eq!("ಠ_ಠ".chars().count(), 3);
而字符串都没有index的索引操作
由于 UTF-8 是一种可变宽度的编码格式,因此字符串中的每个 ASCII 字符都存储在一个字节中,而其他字符可能占用多个字节。因此,在这种格式中,字符串字节的索引并不总是与有效的 Unicode 标量值相关。
如果想更方便地修改一个 String中的特定字节,则需要将其转换为vec<u8>:
fn set_char_inplace(text: &mut String, index: usize, c: char) {
let bytes = unsafe { text.as_mut_vec() };
bytes[index] = c as u8;
}
String可以从另一个方面理解成是一个字节的集合和一些集合的操作方法,存储了一串UTF-8编码的文本。
Rust 中的字符串保证是有效的 UTF-8,可以参考这篇文章的说明Rust为什么要使用UTF-8UTF-8 Everywhere (utf8everywhere.org)
二、什么是&str
str是Rust语言原始类型,也可以称为string slice,通常以借用的方式出现&str
let a: &str = "hello rust";
//it is same with below
let a: 'static str = "hello rust";
// 'static 是一个静态变量,生命周期贯穿整个代码执行
// &str 一般不可直接修改
通常这种都叫做胖指针,胖指针一般有两部分组成:指向bytes的指针和长度
可以通过as_ptr()方法和len()方法获取对应的值
let story = "Hello world";
let ptr = story.as_ptr();
let len = story.len();
三、String和&str
Rust中的String和&str,就像C++中的std::string和char*:
- String 拥有所有内存相关的操作,可以对字符串做很多的处理
- &str 有包含长度
&str包含长度,是因为在操作时候可以使用&str获取到String中的任意一段的内容,例如:
pub fn main() {
let s: String = String::from("hello");
let a: &str = &s;
let b: &str = &s[1..3];
let c = "beaf";
println!("a: {}, len: {}", a, a.len());
println!("b: {}, len: {}", b, b.len());
println!("c: {}, len: {}", c, c.len());
}
四、String 还是 &str ?,该如何选择?
要遵循两个原则:
- 当您需要拥有内存时使用String。例如,您在函数中创建了一个字符串,需要各自操作,需要返回它。
- 当您需要对 String 变量(或代码中的字符串文本)的不可变引用时使用
&str,一般只是引用不会对其修改或其他操作
请记住,当String您将其作为参数传递给函数时,Rust 将遵循move语义原则
fn func(a: String) {
println!("{}", a);
}
pub fn main() {
let v = String::from("hello");
func(v);
println!("v: {}", v);
}
这段代码会出现报错
对于上面一段代码 v 已经move到了func里面,在Rust中可以使用Rust语言的 deref coercing
将&String 转成 &str。如果使用&str,可以修改成:
fn func(a: &str) {
println!("{}", a);
}
pub fn main() {
let v = String::from("hello");
func(&v);
println!("v: {}", v);
}
所以当我们在设计一个API或者方法如果只是对字符串的只读操作,最好还是使用&str
但是当你需要修改一个String的内容时,并不能通过&mut str修改 ,虽然确实存在,但它不是很有用,因为切片无法重新分配其引用。当你需要修改字符串的内容时,需要&mut String作为参数传递,就像上面提到的set_char_inplace例子一样。
在struct中使用字符串时,通常需要修改或操作字符串,最好使用String,但是也可以使用str,只是需要注意rust里面的生命周期是否正确
struct Person<'a> {
name: &'a str,
}
impl<'a> Person<'a> {
fn greet(&self) {
println!("Hello, my name is {}", self.name);
}
}
fn main() {
let person = Person { name: "Coder's Cat" };
person.greet();
}