什么是字符串(String)?
Rust在外围语言中只有一种字符串类型,即字符串切片str,通常以借用模式&str看到。
String类型是Rust的规范库提供的,而不是编码为外围语言,它是一种可增长、可变、可领有的以UTF-8模式编码的字符串类型。 在Rust中援用"strings"时,它们通常指的是String和字符串切片&str类型,但strings不仅仅是这两种类型。但两种类型在Rust的规范库中应用最多,并且String和字符串切片&str都是UTF-8编码的。
Rust的规范库还包含许多其余字符串类型,例如OsString,OsStr,CString和CStr。 图书馆包装箱(Library crates)能够提供更多用于存储字符串数据的选项。为啥这些名称如何都以String或Str结尾? 因为它们指的是领有和借用的变体,就像String和str类型一样。例如,这些字符串类型能够用不同的编码存储文本,或以不同的形式在内存中示意。
创立一个新的字符串
fn main() { let mut s = String::new(); //创立一个新的空字符串名为s,之后就能够应用s。}
通常,咱们会应用一些初始数据作为字符串的结尾。 为此,咱们应用to_string办法,该办法可在实现Display特色的任何类型上应用,就像字符串文字一样。如下所示两个成果完全相同的示例:
//此代码创立一个蕴含初始内容的字符串。fn main() { let data = "初始内容"; let s = data.to_string(); // 该办法也能够间接解决文字: let s = "初始内容".to_string();}
咱们还能够应用函数String::from从字符串文字创立字符串。成果等同于应用to_string:
fn main() { let s = String::from("初始内容");}
认为字符串是UTF-8编码的,所以咱们能够在其中蕴含任何正确编码的数据(不论它长啥样),如下所示:
fn main() { let hello = String::from("你好"); let hello = String::from("Hello"); let hello = String::from(" "); let hello = String::from("Dobrý den"); let hello = String::from(""); let hello = String::from(""); let hello = String::from("こんにちは"); let hello = String::from("안녕하세요"); let hello = String::from("Olá"); let hello = String::from(""); let hello = String::from("Hola");}
更新字符串
如果将更多数据推入字符串中,则字符串的大小会减少,其内容也会发生变化,就像Vec <T>的内容一样。另外,咱们能够不便地应用+运算符或格局!
应用push_str和push附加到字符串
fn main() { let mut s1 = String::from("foo"); let s2 = "bar"; s1.push_str(s2); println!("s2 is {}", s2);}//如果运行的话能够失常打印出后果:s2 is bar。所以push_str办法不会获得所有权
push办法将单个字符作为参数,并将其增加到String。如下示例显示了应用push办法将字母l增加到String的代码:
fn main() { let mut s = String::from("lo"); s.push('l');}
留神应用push办法的时候必须应用单引号,如果应用了双引号的话会呈现谬误:
|
4 | s1.push("l");
| ^^^ expected `char`, found `&str`
与+运算符或格局串联——宏
通常,须要合并两个现有字符串。 一种办法是应用+运算符,下所示:
fn main() { let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // 留神s1已移至此处,无奈在之后应用 println!("s1 + &s2 = {}", s3);}D:\learn\cargo_learn>cargo run Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn) Finished dev [unoptimized + debuginfo] target(s) in 0.60s Running `target\debug\cargo_learn.exe`s1 + &s2 = Hello, world!
+运算符应用add办法,其签名如下所示:fn add(self, s: &str) -> String {
通过上例咱们能够看到咱们能够应用+号运算获取一个新的字符串,然而如果加号比拟多的状况下会很难看到产生了什么。 对于更简单的字符串组合,咱们能够应用format!
宏:
fn main() { let s1 = String::from("你"); let s2 = String::from("好"); let s3 = String::from("啊!"); let s = format!("{}-{}-{}", s1, s2, s3); println!("{}", s1); println!("{}", s)}D:\learn\cargo_learn>cargo run Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn) Finished dev [unoptimized + debuginfo] target(s) in 0.59s Running `target\debug\cargo_learn.exe`你你-好-啊!
能够看到应用format!
只是参考借阅变量,并不会获取变量的所有权
索引到字符串
咱们是否能够像js中获取js某一个片段的形式一样在rust中应用呢,比方:
fn main() { let s1 = String::from("hello"); let h = s1[0]; println!("{}", h);}
答案是不能够的:
D:\learn\cargo_learn>cargo run Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)error[E0277]: the type `std::string::String` cannot be indexed by `{integer}` --> src\main.rs:3:13 |3 | let h = s1[0]; | ^^^^^ `std::string::String` cannot be indexed by `{integer}` | = help: the trait `std::ops::Index<{integer}>` is not implemented for `std::string::String`error: aborting due to previous errorFor more information about this error, try `rustc --explain E0277`.error: could not compile `cargo_learn`.To learn more, run the command again with --verbose.
为什么不能够?先来看看Rust是如何在内存中存储字符串的。
外部代表
字符串是Vec<u8>的包装。先看一下通过正确编码的UTF-8示例字符串:let hello = String::from("Hola");
在这种状况下,len将为4,这意味着存储字符串“ ”Hola的向量的长度为4个字节。 以UTF-8编码时,每个字母占用1个字节。 但要是这样呢?let hello = String::from(""); //(请留神,此字符串以西里尔字母大写Ze结尾,而不是阿拉伯数字3。)
当询问该字符串有多长时,咱们可能会说12。然而,Rust的答案是24:这是在UTF-8中编码“”所需的字节数,因为该字符串中的每个Unicode标量值都占用2个字节的存储空间。
因而,字符串字节的索引并不总是与无效的Unicode标量值相干。 因而为了演示,看看以下有效的Rust代码:
let hello = "";let answer = &hello[0];
答案的价值是什么? 应该是,第一个字母吗? 当以UTF-8编码时,的第一个字节为208,第二个字节为151,因而答案实际上应为208,但208自身不是无效字符。如果用户要求输出此字符串的第一个字母,则返回208可能不是用户想要的。 然而,这是Rust领有字节索引0的惟一数据。即便字符串仅蕴含拉丁字母,用户也通常不心愿返回字节值:如果&"hello"[0]是返回字节值的无效代码 ,它将返回104,而不是h。为了防止返回意外的值并导致可能不会立刻发现的谬误,Rust齐全不编译该代码,并防止了开发过程晚期的误会。
字节(bytes)和标量值(scalar values)以及字素簇(grapheme clusters)!
对于UTF-8的另一点是,从Rust的角度来看,实际上有三种相干的形式来查看字符串:字节,标量值和字素簇(最靠近字母的货色)。
如果咱们看一下用梵文脚本编写的北印度语单词“”,它会存储为u8值的向量,如下所示:
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,224, 165, 135]
那是18字节,这是计算机最终存储此数据的形式。 如果咱们将它们视为Unicode标量值(即Rust的char类型),则这些字节如下所示:['', '', '', '', '', '']
这里有六个char值,然而第四个和第六个不是字母:它们是变音符号,它们本人没有意义。 最初,如果咱们将它们视为字素簇,咱们将失去一个人所说的组成印地语单词的四个字母:["", "", "", ""]
Rust提供了不同的形式来解释计算机存储的原始字符串数据,以便每个程序都能够抉择所需的解释形式,而与数据所用的语言无关。
Rust不容许咱们索引到String中以获取字符的最初一个起因是索引操作总是须要恒定的工夫(O(1))。 然而用String不能保障性能,因为Rust必须从头到尾遍历所有内容以确定有多少个无效字符。
切片字符串
将字符串索引化通常是一个坏主意,因为不分明字符串索引操作的返回类型应该是什么:字节值,字符,字形簇或字符串切片。 因而,Rust要求更具体地说明是否真的须要应用索引来创立字符串切片。为了更具体地批示索引并批示要应用字符串切片,而不是应用带单个数字的[]进行索引,能够将[]与范畴联合应用以创立蕴含特定字节的字符串切片:
#![allow(unused_variables)]fn main() { let hello = ""; let s = &hello[0..4]; println!("{}",s);}D:\learn\cargo_learn>cargo run Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn) Finished dev [unoptimized + debuginfo] target(s) in 0.55s Running `target\debug\cargo_learn.exe`
遍历字符串的办法
侥幸的是,您能够通过其余形式拜访字符串中的元素。
如果您须要对单个Unicode标量值执行操作,最好的办法是应用chars办法。 在“”上调用char会拆散出并返回char类型的六个值,您能够遍历后果以拜访每个元素:
#![allow(unused_variables)]fn main() { for c in "".chars() { println!("{}", c); }}D:\learn\cargo_learn>cargo run Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn) Finished dev [unoptimized + debuginfo] target(s) in 0.52s Running `target\debug\cargo_learn.exe`
bytes办法返回每个原始字节,这可能适宜咱们的初衷:
#![allow(unused_variables)]fn main() { for b in "".bytes() { println!("{}", b); }}
这段代码将打印出组成此字符串的18个字节:
224164// --snip--165135
无效的Unicode标量值可能由1个以上的字节组成。
从字符串获取字素簇很简单,因而rust规范库不提供此性能。然而咱们能够在crates.io上获取应用
字符串不是那么简略
总而言之,字符串很简单。Rust抉择将正确处理String数据作为所有Rust程序的默认行为,这意味着咱们必须在解决UTF-8数据方面投入更多精力,尽管这种折衷会带来更多的字符串复杂性,然而这能够避免在开发生命周期的前期不得不解决波及非ASCII字符的谬误。