String

Rust字符串

String和 &str

Rust有两种主要的字符串类型:String和 &str。有什么区别?

  • &str是一个简单的字符串。当你写let my_variable = "Hello, world!",你创建了&str,它的速度非常快。
  • String字符串是更复杂的字符串。它有点慢,并且功能更多。字符串是一个指针,在堆上有数据。

&strString都是UTF-8,譬如:

// Rust 的 string 被表达为:&'static str,所有的用""包裹起来的字符串,都被声明成了一个不可变,静态的字符串。
let x = "Hello";
let x:&'static str = "Hello";

fn main() {
    let name = "서태지"; // This is a Korean name. No problem, because a &str is UTF-8.
    let other_name = String::from("Adrian Fahrenheit Țepeș"); // Ț and ș are no problem in UTF-8.
}

您甚至可以借助UTF-8编写表情符号。

fn main() {
    let name = "😂";
    println!("My name is actually {}", name);
}

那么为什么我们在str前面需要一个 & 而不是String呢?因为,str是动态类型,例如,名字 “서태지” 以及 “Adrian Fahrenheit Țepeș” 堆栈上的大小不同:

fn main() {
    println!("A String is always {:?} bytes. It is Sized.", std::mem::size_of::<String>()); // std::mem::size_of::<Type>() gives you the size in bytes of a type
    println!("And an i8 is always {:?} bytes. It is Sized.", std::mem::size_of::<i8>());
    println!("And an f64 is always {:?} bytes. It is Sized.", std::mem::size_of::<f64>());
    println!("But a &str? It can be anything. '서태지' is {:?} bytes. It is not Sized.", std::mem::size_of_val("서태지")); // std::mem::size_of_val() gives you the size in bytes of a variable
    println!("And 'Adrian Fahrenheit Țepeș' is {:?} bytes. It is not Sized.", std::mem::size_of_val("Adrian Fahrenheit Țepeș"));
}

这就是为什么我们需要一个 & 的原因,因为 & 会创建一个指针,而Rust知道指针的大小。因此,指针进入堆栈。如果我们写了strRust将不知道要做什么,因为它不知道大小。有很多方法可以制作字符串。

类型互转

这里有一些:

  • String::from("This is the string text"); 这是用于String的方法,该方法采用文本并创建String
  • "This is the string text".to_string(),这是 &str的方法,使它成为String
  • format!宏。这就像println!除了它创建一个String而不是打印。因此,您可以执行以下操作:
fn main() {
    let my_name = "Billybrobby";
    let my_country = "USA";
    let my_home = "Korea";

    let together = format!(
        "I am {} and I come from {} but I live in {}.",
        my_name, my_country, my_home
    );
}

现在我们有了一个在一起命名的字符串,但尚未打印出来。此外,我们还可以使用 .into() 来创建字符串,某些类型可以使用from.into()轻松地与其他类型进行转换;如果您有From,那么您也有.into()from更清晰,因为您已经知道类型:您知道 String::from("Some str") 是来自 &strString。但是使用.into(),有时编译器不知道:

fn main() {
    let my_string = "Try to make this a String".into(); // ⚠️
}

// Rust doesn't know what type you want, because many types can be made from a &str.

error[E0282]: type annotations needed
 --> src\main.rs:2:9
  |
2 |     let my_string = "Try to make this a String".into();
  |         ^^^^^^^^^ consider giving `my_string` a type

fn main() {
    let my_string: String = "Try to make this a String".into();
}

我们也可以使用 &* 符号将String转化为&str类型:

fn use_str(s: &str) {
    println!("I am: {}", s);
}

fn main() {
    let s = "Hello".to_string();
    use_str(&*s);
}

首先呢,&* 是两个符号 &* 的组合,按照Rust的运算顺序,先对String进行Deref,也就是 * 操作。由于String实现了 impl Deref<Target=str> for String,这相当于一个运算符重载,所以你就能通过 * 获得一个str类型。但是我们知道,单独的str是不能在Rust里直接存在的,因此,我们需要先给他进行&操作取得&str这个结果。

索引访问

有人会把Rust中的字符串和其惯用的字符串等同起来,于是就出现了如下代码

let x = "hello".to_string();
x[1]; //编译错误!

Rust的字符串实际上是不支持通过下标访问的,但是呢,我们可以通过将其转变成数组的方式访问

let x = "哎哟我去".to_string();
for i in x.as_bytes() {
    print!("{} ", i);
}

println!("");

for i in x.chars() {
    print!("{}", i);
}

x.chars().nth(2);

格式化字符串

Rust采取了一种类似Python里面format的用法,其核心组成是五个宏和两个trait:format!format_arg!print!println!write!;DebugDisplay。相信你们在写Rust版本的Hello World的时候用到了print!或者println!这两个宏,但是其实最核心的是format!,前两个宏只不过将format!的结果输出到了console而已。

fn main() {
    let s = format!("{1}是个有着{0:>0width$}KG重,{height:?}cm高的大胖子",
                    81, "wayslog", width=4, height=178);
    // 我被逼的牺牲了自己了……
    print!("{}", s);
}
format_string := <text> [ format <text> ] *
format := '{' [ argument ] [ ':' format_spec ] '}'
argument := integer | identifier

format_spec := [[fill]align][sign]['#'][0][width]['.' precision][type]
fill := character
align := '<' | '^' | '>'
sign := '+' | '-'
width := count
precision := count | '*'
type := identifier | ''
count := parameter | integer
parameter := integer '$'

字符串切片

另一个没有所有权的数据类型是sliceslice允许你引用集合中一段连续的元素序列,而不用引用整个集合。字符串slice(string slice)是String中一部分值的引用,它看起来像这样:

let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];

这类似于引用整个String不过带有额外的[0..5]部分。它不是对整个String的引用,而是对部分String的引用。可以使用一个由中括号中的[starting_index..ending_index]指定的range创建一个slice,其中starting_indexslice的第一个位置,ending_index则是slice最后一个位置的后一个值。在其内部,slice的数据结构存储了slice的开始位置和长度,长度对应于ending_index减去starting_index的值。所以对于 let world = &s[6..11]; 的情况,world将是一个包含指向s7个字节(从1开始)的指针和长度值5slice

引用了部分 String 的字符串 Slice

对于Rust.. range语法,如果想要从第一个索引(0)开始,可以不写两个点号之前的值。换句话说,如下两个语句是相同的:

let s = String::from("hello");

let slice = &s[0..2];
let slice = &s[..2];

依此类推,如果slice包含String的最后一个字节,也可以舍弃尾部的数字。这意味着如下也是相同的:

let s = String::from("hello");

let len = s.len();

let slice = &s[3..len];
let slice = &s[3..];

也可以同时舍弃这两个值来获取整个字符串的slice。所以如下亦是相同的:

let s = String::from("hello");

let len = s.len();

let slice = &s[0..len];
let slice = &s[..];

字符串slice range的索引必须位于有效的UTF-8字符边界内,如果尝试从一个多字节字符的中间位置创建字符串slice,则程序将会因错误而退出。

上一页
下一页