标准库之旅

现在你已经知道了很多Rust的知识,你将能够理解标准库里面的大部分东西。它里面的代码已经不是那么可怕了。让我们来看看它里面一些我们还没有学过的部分。本篇游记将介绍标准库的大部分部分,你不需要安装Rust。我们将重温很多我们已经知道的内容,这样我们就可以更深入地学习它们。

数组

关于数组需要注意的一点是,它们没有实现Iterator.。这意味着,如果你有一个数组,你不能使用for。但是你可以对它们使用 .iter() 这样的方法。或者你可以使用&来得到一个切片。实际上,如果你尝试使用for,编译器会准确地告诉你。

fn main() {
    // ⚠️
    let my_cities = ["Beirut", "Tel Aviv", "Nicosia"];

    for city in my_cities {
        println!("{}", city);
    }
}

消息是:

error[E0277]: `[&str; 3]` is not an iterator
 --> src\main.rs:5:17
  |
  |                 ^^^^^^^^^ borrow the array with `&` or call `.iter()` on it to iterate over it

所以让我们试试这两种方法。它们的结果是一样的。

fn main() {
    let my_cities = ["Beirut", "Tel Aviv", "Nicosia"];

    for city in &my_cities {
        println!("{}", city);
    }
    for city in my_cities.iter() {
        println!("{}", city);
    }
}

这个打印:

Beirut
Tel Aviv
Nicosia
Beirut
Tel Aviv
Nicosia

如果你想从一个数组中获取变量,你可以把它们的名字放在 [] 中来解构它。这与在 match 语句中使用元组或从结构体中获取变量是一样的。

fn main() {
    let my_cities = ["Beirut", "Tel Aviv", "Nicosia"];
    let [city1, city2, city3] = my_cities;
    println!("{}", city1);
}

打印出Beirut.

char

您可以使用.escape_unicode()的方法来获取char的Unicode号码。

fn main() {
    let korean_word = "청춘예찬";
    for character in korean_word.chars() {
        print!("{} ", character.escape_unicode());
    }
}

这将打印出 u{ccad} u{cd98} u{c608} u{cc2c}

你可以使用 From trait从 u8 中得到一个字符,但对于 u32,你使用 TryFrom,因为它可能无法工作。u32中的数字比Unicode中的字符多很多。我们可以通过一个简单的演示来了解。

use std::convert::TryFrom; // You need to bring TryFrom in to use it
use rand::prelude::*;      // We will use random numbers too

fn main() {
    let some_character = char::from(99); // This one is easy - no need for TryFrom
    println!("{}", some_character);

    let mut random_generator = rand::thread_rng();
    // This will try 40,000 times to make a char from a u32.
    // The range is 0 (std::u32::MIN) to u32's highest number (std::u32::MAX). If it doesn't work, we will give it '-'.
    for _ in 0..40_000 {
        let bigger_character = char::try_from(random_generator.gen_range(std::u32::MIN..std::u32::MAX)).unwrap_or('-');
        print!("{}", bigger_character)
    }
}

几乎每次都会生成一个-。这是你会看到的那种输出的一部分。

------------------------------------------------------------------------𤒰---------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-------------------------------------------------------------춗--------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
------------򇍜----------------------------------------------------

所以,你要用TryFrom是件好事。

另外,从2020年8月底开始,你现在可以从char中得到一个String。(String实现了From<char>)只要写String::from(),然后在里面放一个char

整数

这些类型的数学方法有很多,另外还有一些其他的方法。下面是一些最有用的。

.checked_add(), .checked_sub(), .checked_mul(), .checked_div(). 如果你认为你可能会得到一个不适合类型的数字,这些都是不错的方法。它们会返回一个 Option,这样你就可以安全地检查你的数学计算是否正常,而不会让程序崩溃。

fn main() {
    let some_number = 200_u8;
    let other_number = 200_u8;

    println!("{:?}", some_number.checked_add(other_number));
    println!("{:?}", some_number.checked_add(1));
}

这个打印:

None
Some(201)

你会注意到,在整数的页面上,经常说rhs。这意味着 "右边",也就是你做一些数学运算时的右操作数。比如在5 + 6中,5在左边,6在右边,所以6就是rhs。这个不是关键词,但是你会经常看到,所以知道就好。

说到这里,我们来学习一下如何实现Add。在你实现了Add之后,你可以在你创建的类型上使用+。你需要自己实现Add,因为add可以表达很多意思。这是标准库页面中的例子。

#![allow(unused)]
fn main() {
use std::ops::Add; // first bring in Add

#[derive(Debug, Copy, Clone, PartialEq)] // PartialEq is probably the most important part here. You want to be able to compare numbers
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Self; // Remember, this is called an "associated type": a "type that goes together".
                        // In this case it's just another Point

    fn add(self, other: Self) -> Self {
        Self {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}
}

现在让我们为自己的类型实现Add。让我们想象一下,我们想把两个国家加在一起,这样我们就可以比较它们的经济。它看起来像这样:

use std::fmt;
use std::ops::Add;

#[derive(Clone)]
struct Country {
    name: String,
    population: u32,
    gdp: u32, // This is the size of the economy
}

impl Country {
    fn new(name: &str, population: u32, gdp: u32) -> Self {
        Self {
            name: name.to_string(),
            population,
            gdp,
        }
    }
}

impl Add for Country {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            name: format!("{} and {}", self.name, other.name), // We will add the names together,
            population: self.population + other.population, // and the population,
            gdp: self.gdp + other.gdp,   // and the GDP
        }
    }
}

impl fmt::Display for Country {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "In {} are {} people and a GDP of ${}", // Then we can print them all with just {}
            self.name, self.population, self.gdp
        )
    }
}

fn main() {
    let nauru = Country::new("Nauru", 10_670, 160_000_000);
    let vanuatu = Country::new("Vanuatu", 307_815, 820_000_000);
    let micronesia = Country::new("Micronesia", 104_468, 367_000_000);

    // We could have given Country a &str instead of a String for the name. But we would have to write lifetimes everywhere
    // and that would be too much for a small example. Better to just clone them when we call println!.
    println!("{}", nauru.clone());
    println!("{}", nauru.clone() + vanuatu.clone());
    println!("{}", nauru + vanuatu + micronesia);
}

这个打印:

In Nauru are 10670 people and a GDP of $160000000
In Nauru and Vanuatu are 318485 people and a GDP of $980000000
In Nauru and Vanuatu and Micronesia are 422953 people and a GDP of $1347000000

以后在这段代码中,我们可以把.fmt()改成更容易阅读的数字显示。

另外三个叫SubMulDiv,实现起来基本一样。+=-=*=/=,只要加上Assign:AddAssignSubAssignMulAssignDivAssign即可。你可以看到完整的列表这里,因为还有很多。例如 % 被称为 Rem, - 被称为 Neg, 等等。

浮点数

f32f64有非常多的方法,你在做数学计算的时候会用到。我们不看这些,但这里有一些你可能会用到的方法。它们分别是 .floor(), .ceil(), .round(), 和 .trunc(). 所有这些方法都返回一个 f32f64,它像一个整数,小数点后面是 0。它们是这样做的。

  • .floor(): 给你下一个最低的整数.
  • .ceil(): 给你下一个最高的整数。
  • .round(): 如果小数部分大于等于0.5,返回数值加1;如果小数部分小于0.5,返回相同数值。这就是所谓的四舍五入,因为它给你一个 "舍入"的数字(一个数字的简短形式)。
  • .trunc():只是把小数点号后的部分截掉。Truncate是 "截断"的意思。

这里有一个简单的函数来打印它们。

fn four_operations(input: f64) {
    println!(
"For the number {}:
floor: {}
ceiling: {}
rounded: {}
truncated: {}\n",
        input,
        input.floor(),
        input.ceil(),
        input.round(),
        input.trunc()
    );
}

fn main() {
    four_operations(9.1);
    four_operations(100.7);
    four_operations(-1.1);
    four_operations(-19.9);
}

这个打印:

For the number 9.1:
floor: 9
ceiling: 10
rounded: 9 // because less than 9.5
truncated: 9

For the number 100.7:
floor: 100
ceiling: 101
rounded: 101 // because more than 100.5
truncated: 100

For the number -1.1:
floor: -2
ceiling: -1
rounded: -1
truncated: -1

For the number -19.9:
floor: -20
ceiling: -19
rounded: -20
truncated: -19

f32f64 有一个叫做 .max().min() 的方法,可以得到两个数字中较大或较小的数字。(对于其他类型,你可以直接使用std::cmp::maxstd::cmp::min。)下面是用.fold()来得到最高或最低数的方法。你又可以看到,.fold()不仅仅是用来加数字的。

fn main() {
    let my_vec = vec![8.0_f64, 7.6, 9.4, 10.0, 22.0, 77.345, 10.22, 3.2, -7.77, -10.0];
    let maximum = my_vec.iter().fold(f64::MIN, |current_number, next_number| current_number.max(*next_number)); // Note: start with the lowest possible number for an f64.
    let minimum = my_vec.iter().fold(f64::MAX, |current_number, next_number| current_number.min(*next_number)); // And here start with the highest possible number
    println!("{}, {}", maximum, minimum);
}

bool

在 Rust 中,如果你愿意,你可以把 bool 变成一个整数,因为这样做是安全的。但你不能反过来做。如你所见,true变成了1,false变成了0。

fn main() {
    let true_false = (true, false);
    println!("{} {}", true_false.0 as u8, true_false.1 as i32);
}

这将打印出1 0。如果你告诉编译器类型,也可以使用 .into()

fn main() {
    let true_false: (i128, u16) = (true.into(), false.into());
    println!("{} {}", true_false.0, true_false.1);
}

这打印的是一样的东西。

从Rust 1.50(2021年2月发布)开始,有一个叫做 then()的方法,它将一个 bool变成一个 Option。使用then()时需要一个闭包,如果item是true,闭包就会被调用。同时,无论从闭包中返回什么,都会进入Option中。下面是一个小例子:

fn main() {

    let (tru, fals) = (true.then(|| 8), false.then(|| 8));
    println!("{:?}, {:?}", tru, fals);
}

这个打印 Some(8), None

下面是一个较长的例子:

fn main() {
    let bool_vec = vec![true, false, true, false, false];

    let option_vec = bool_vec
        .iter()
        .map(|item| {
            item.then(|| { // Put this inside of map so we can pass it on
                println!("Got a {}!", item);
                "It's true, you know" // This goes inside Some if it's true
                                      // Otherwise it just passes on None
            })
        })
        .collect::<Vec<_>>();

    println!("Now we have: {:?}", option_vec);

    // That printed out the Nones too. Let's filter map them out in a new Vec.
    let filtered_vec = option_vec.into_iter().filter_map(|c| c).collect::<Vec<_>>();

    println!("And without the Nones: {:?}", filtered_vec);
}

将打印:

Got a true!
Got a true!
Now we have: [Some("It\'s true, you know"), None, Some("It\'s true, you know"), None, None]
And without the Nones: ["It\'s true, you know", "It\'s true, you know"]

Vec

Vec有很多方法我们还没有看。先说说.sort().sort()一点都不奇怪。它使用&mut self来对一个向量进行排序。

fn main() {
    let mut my_vec = vec![100, 90, 80, 0, 0, 0, 0, 0];
    my_vec.sort();
    println!("{:?}", my_vec);
}

这样打印出来的是[0, 0, 0, 0, 0, 80, 90, 100]。但还有一种更有趣的排序方式叫.sort_unstable(),它通常更快。它之所以更快,是因为它不在乎排序前后相同数字的先后顺序。在常规的.sort()中,你知道最后的0, 0, 0, 0, 0会在.sort()之后的顺序相同。但是.sort_unstable()可能会把最后一个0移到索引0,然后把第三个最后的0移到索引2,等等。

.dedup()的意思是 "去重复"。它将删除一个向量中相同的元素,但只有当它们彼此相邻时才会删除。接下来这段代码不会只打印"sun", "moon"

fn main() {
    let mut my_vec = vec!["sun", "sun", "moon", "moon", "sun", "moon", "moon"];
    my_vec.dedup();
    println!("{:?}", my_vec);
}

它只是把另一个 "sun"旁边的 "sun"去掉,然后把一个 "moon"旁边的 "moon"去掉,再把另一个 "moon"旁边的 "moon"去掉。结果是 ["sun", "moon", "sun", "moon"].

如果你想把每个重复的东西都去掉,就先.sort():

fn main() {
    let mut my_vec = vec!["sun", "sun", "moon", "moon", "sun", "moon", "moon"];
    my_vec.sort();
    my_vec.dedup();
    println!("{:?}", my_vec);
}

结果:["moon", "sun"].

String

你会记得,String有点像Vec。它很像Vec,你可以调用很多相同的方法。比如说,你可以用String::with_capacity()创建一个,如果你需要多次用.push()推一个char,或者用.push_str()推一个&str。下面是一个有多次内存分配的String的例子。

fn main() {
    let mut push_string = String::new();
    let mut capacity_counter = 0; // capacity starts at 0
    for _ in 0..100_000 { // Do this 100,000 times
        if push_string.capacity() != capacity_counter { // First check if capacity is different now
            println!("{}", push_string.capacity()); // If it is, print it
            capacity_counter = push_string.capacity(); // then update the counter
        }
        push_string.push_str("I'm getting pushed into the string!"); // and push this in every time
    }
}

这个打印:

35
70
140
280
560
1120
2240
4480
8960
17920
35840
71680
143360
286720
573440
1146880
2293760
4587520

我们不得不重新分配(把所有东西复制过来)18次。但既然我们知道了最终的容量,我们可以马上设置容量,不需要重新分配:只设置一次String容量就够了。

fn main() {
    let mut push_string = String::with_capacity(4587520); // We know the exact number. Some different big number could work too
    let mut capacity_counter = 0;
    for _ in 0..100_000 {
        if push_string.capacity() != capacity_counter {
            println!("{}", push_string.capacity());
            capacity_counter = push_string.capacity();
        }
        push_string.push_str("I'm getting pushed into the string!");
    }
}

而这个打印4587520。完美的! 我们再也不用分配了。

当然,实际长度肯定比这个小。如果你试了100001次,101000次等等,还是会说4587520。这是因为每次的容量都是之前的2倍。不过我们可以用.shrink_to_fit()来缩小它(和Vec一样)。我们的String已经非常大了,我们不想再给它增加任何东西,所以我们可以把它缩小一点。但是只有在你有把握的情况下才可以这样做:下面是原因。

fn main() {
    let mut push_string = String::with_capacity(4587520);
    let mut capacity_counter = 0;
    for _ in 0..100_000 {
        if push_string.capacity() != capacity_counter {
            println!("{}", push_string.capacity());
            capacity_counter = push_string.capacity();
        }
        push_string.push_str("I'm getting pushed into the string!");
    }
    push_string.shrink_to_fit();
    println!("{}", push_string.capacity());
    push_string.push('a');
    println!("{}", push_string.capacity());
    push_string.shrink_to_fit();
    println!("{}", push_string.capacity());
}

这个打印:

4587520
3500000
7000000
3500001

所以首先我们的大小是4587520,但我们没有全部使用。我们用了.shrink_to_fit(),然后把大小降到了3500000。但是我们忘记了我们需要推上一个 a。当我们这样做的时候,Rust 看到我们需要更多的空间,给了我们双倍的空间:现在是 7000000。Whoops! 所以我们又调用了.shrink_to_fit(),现在又回到了3500001

.pop()String有用,就像对Vec一样。

fn main() {
    let mut my_string = String::from(".daer ot drah tib elttil a si gnirts sihT");
    loop {
        let pop_result = my_string.pop();
        match pop_result {
            Some(character) => print!("{}", character),
            None => break,
        }
    }
}

这打印的是This string is a little bit hard to read.,因为它是从最后一个字符开始的。

.retain()是一个使用闭包的方法,这对String来说是罕见的。就像在迭代器上的.filter()一样。

fn main() {
    let mut my_string = String::from("Age: 20 Height: 194 Weight: 80");
    my_string.retain(|character| character.is_alphabetic() || character == ' '); // Keep if a letter or a space
    dbg!(my_string); // Let's use dbg!() for fun this time instead of println!
}

这个打印:

[src\main.rs:4] my_string = "Age  Height  Weight "

OsString和CString

std::ffistd的一部分,它帮助你将Rust与其他语言或操作系统一起使用。它有OsStringCString这样的类型,它们就像操作系统的String或语言C的String一样,它们各自也有自己的&str类型:OsStrCStrffi的意思是 "foreign function interface"(外部函数接口)。

当你必须与一个没有Unicode的操作系统一起工作时,你可以使用OsString。所有的Rust字符串都是unicode,但不是每个操作系统支持。下面是标准库中关于为什么我们有OsString的简单英文解释。

  • Unix系统(Linux等)上的字符串可能是很多没有0的字节组合在一起。而且有时你会把它们读成Unicode UTF-8。
  • Windows上的字符串可能是由随机的16位值组成的,没有0。有时你会把它们读成Unicode UTF-16。
  • 在Rust中,字符串总是有效的UTF-8,其中可能包含0。

所以,OsString被设计为支持它们读取。

你可以用一个OsString做所有常规的事情,比如OsString::from("Write something here")。它还有一个有趣的方法,叫做 .into_string(),试图把自己变成一个常规的 String。它返回一个 Result,但 Err 部分只是原来的 OsString

#![allow(unused)]
fn main() {
// 🚧
pub fn into_string(self) -> Result<String, OsString>
}

所以如果不行的话,那你就把它找回来。你不能调用.unwrap(),因为它会崩溃,但是你可以使用match来找回OsString。我们通过调用不存在的方法来测试一下。

use std::ffi::OsString;

fn main() {
    // ⚠️
    let os_string = OsString::from("This string works for your OS too.");
    match os_string.into_string() {
        Ok(valid) => valid.thth(),           // Compiler: "What's .thth()??"
        Err(not_valid) => not_valid.occg(),  // Compiler: "What's .occg()??"
    }
}

然后编译器准确地告诉我们我们想知道的东西。

error[E0599]: no method named `thth` found for struct `std::string::String` in the current scope
 --> src/main.rs:6:28
  |
6 |         Ok(valid) => valid.thth(),
  |                            ^^^^ method not found in `std::string::String`

error[E0599]: no method named `occg` found for struct `std::ffi::OsString` in the current scope
 --> src/main.rs:7:37
  |
7 |         Err(not_valid) => not_valid.occg(),
  |                                     ^^^^ method not found in `std::ffi::OsString`

我们可以看到,valid的类型是Stringnot_valid的类型是OsString

Mem

std::mem有一些非常有趣的方法。我们已经看到了一些,比如.size_of().size_of_val().drop()

use std::mem;

fn main() {
    println!("{}", mem::size_of::<i32>());
    let my_array = [8; 50];
    println!("{}", mem::size_of_val(&my_array));
    let mut some_string = String::from("You can drop a String because it's on the heap");
    mem::drop(some_string);
    // some_string.clear();   If we did this it would panic
}

这个打印:

4
200

下面是mem中的一些其他方法。

swap(): 用这个方法你可以交换两个变量之间的值。你可以通过为每个变量创建一个可变引用来做。当你有两个东西想交换,而Rust因为借用规则不让你交换时,这很有帮助。或者只是当你想快速切换两个东西的时候。

这里有一个例子。

use std::{mem, fmt};

struct Ring { // Create a ring from Lord of the Rings
    owner: String,
    former_owner: String,
    seeker: String, // seeker means "person looking for it"
}

impl Ring {
    fn new(owner: &str, former_owner: &str, seeker: &str) -> Self {
        Self {
            owner: owner.to_string(),
            former_owner: former_owner.to_string(),
            seeker: seeker.to_string(),
        }
    }
}

impl fmt::Display for Ring { // Display to show who has it and who wants it
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{} has the ring, {} used to have it, and {} wants it", self.owner, self.former_owner, self.seeker)
        }
}

fn main() {
    let mut one_ring = Ring::new("Frodo", "Gollum", "Sauron");
    println!("{}", one_ring);
    mem::swap(&mut one_ring.owner, &mut one_ring.former_owner); // Gollum got the ring back for a second
    println!("{}", one_ring);
}

这将打印:

Frodo has the ring, Gollum used to have it, and Sauron wants it
Gollum has the ring, Frodo used to have it, and Sauron wants it

replace():这个就像swap一样,其实里面也用了swap,你可以看到。

#![allow(unused)]
fn main() {
pub fn replace<T>(dest: &mut T, mut src: T) -> T {
    swap(dest, &mut src);
    src
}
}

所以它只是做了一个交换,然后返回另一个元素。有了这个,你就用你放进去的其他东西来替换这个值。因为它返回的是旧的值,所以你应该用let来使用它。下面是一个简单的例子。

use std::mem;

struct City {
    name: String,
}

impl City {
    fn change_name(&mut self, name: &str) {
        let old_name = mem::replace(&mut self.name, name.to_string());
        println!(
            "The city once called {} is now called {}.",
            old_name, self.name
        );
    }
}

fn main() {
    let mut capital_city = City {
        name: "Constantinople".to_string(),
    };
    capital_city.change_name("Istanbul");
}

这样就会打印出The city once called Constantinople is now called Istanbul.

有一个函数叫.take(),和.replace()一样,但它在元素中留下了默认值。 你会记得,默认值通常是0、""之类的东西。这里是签名。

#![allow(unused)]
fn main() {
// 🚧
pub fn take<T>(dest: &mut T) -> T
where
    T: Default,
}

所以你可以做这样的事情。

use std::mem;

fn main() {
    let mut number_vec = vec![8, 7, 0, 2, 49, 9999];
    let mut new_vec = vec![];

    number_vec.iter_mut().for_each(|number| {
        let taker = mem::take(number);
        new_vec.push(taker);
    });

    println!("{:?}\n{:?}", number_vec, new_vec);
}

你可以看到,它将所有数字都替换为0:没有删除任何索引。

[0, 0, 0, 0, 0, 0]
[8, 7, 0, 2, 49, 9999]

当然,对于你自己的类型,你可以把Default实现成任何你想要的类型。我们来看一个例子,我们有一个Bank和一个Robber。每次他抢了Bank,他就会在桌子上拿到钱。但是办公桌可以随时从后面拿钱,所以它永远有50。我们将为此自制一个类型,所以它将永远有50。下面是它的工作原理。

use std::mem;
use std::ops::{Deref, DerefMut}; // We will use this to get the power of u32

struct Bank {
    money_inside: u32,
    money_at_desk: DeskMoney, // This is our "smart pointer" type. It has its own default, but it will use u32
}

struct DeskMoney(u32);

impl Default for DeskMoney {
    fn default() -> Self {
        Self(50) // default is always 50, not 0
    }
}

impl Deref for DeskMoney { // With this we can access the u32 using *
    type Target = u32;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for DeskMoney { // And with this we can add, subtract, etc.
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Bank {
    fn check_money(&self) {
        println!(
            "There is ${} in the back and ${} at the desk.\n",
            self.money_inside, *self.money_at_desk // Use * so we can just print the u32
        );
    }
}

struct Robber {
    money_in_pocket: u32,
}

impl Robber {
    fn check_money(&self) {
        println!("The robber has ${} right now.\n", self.money_in_pocket);
    }

    fn rob_bank(&mut self, bank: &mut Bank) {
        let new_money = mem::take(&mut bank.money_at_desk); // Here it takes the money, and leaves 50 because that is the default
        self.money_in_pocket += *new_money; // Use * because we can only add u32. DeskMoney can't add
        bank.money_inside -= *new_money;    // Same here
        println!("She robbed the bank. She now has ${}!\n", self.money_in_pocket);
    }
}

fn main() {
    let mut bank_of_klezkavania = Bank { // Set up our bank
        money_inside: 5000,
        money_at_desk: DeskMoney(50),
    };
    bank_of_klezkavania.check_money();

    let mut robber = Robber { // Set up our robber
        money_in_pocket: 50,
    };
    robber.check_money();

    robber.rob_bank(&mut bank_of_klezkavania); // Rob, then check money
    robber.check_money();
    bank_of_klezkavania.check_money();

    robber.rob_bank(&mut bank_of_klezkavania); // Do it again
    robber.check_money();
    bank_of_klezkavania.check_money();

}

这将打印:

There is $5000 in the back and $50 at the desk.

The robber has $50 right now.

She robbed the bank. She now has $100!

The robber has $100 right now.

There is $4950 in the back and $50 at the desk.

She robbed the bank. She now has $150!

The robber has $150 right now.

There is $4900 in the back and $50 at the desk.

你可以看到桌子上总是有50美元。

Prelude

标准库也有一个prelude,这就是为什么你不用写use std::vec::Vec这样的东西来创建一个Vec。你可以在这里看到所有这些元素,并且大致了解:

  • std::marker::{Copy, Send, Sized, Sync, Unpin}. 你以前没有见过Unpin,因为几乎每一种类型都会用到它(比如Sized,也很常见)。"Pin"的意思是不让东西动。在这种情况下,Pin意味着它在内存中不能移动,但大多数元素都有Unpin,所以你可以移动。这就是为什么像std::mem::replace这样的函数能用,因为它们没有被钉住。
  • std::ops::{Drop, Fn, FnMut, FnOnce}.
  • std::mem::drop
  • std::boxed::Box.
  • std::borrow::ToOwned. 你之前用Cow看到过一点,它可以把借来的内容变成自己的。它使用.to_owned()来实现这个功能。你也可以在&str上使用.to_owned(),得到一个String,对于其他借来的值也是一样。
  • std::clone::Clone
  • std::cmp::{PartialEq, PartialOrd, Eq, Ord}.
  • std::convert::{AsRef, AsMut, Into, From}.
  • std::default::Default.
  • std::iter::{Iterator, Extend, IntoIterator, DoubleEndedIterator, ExactSizeIterator}. 我们之前用.rev()来做迭代器:这实际上是做了一个DoubleEndedIteratorExactSizeIterator只是类似于0..10的东西:它已经知道自己的.len()是10。其他迭代器不知道它们的长度是肯定的。
  • std::option::Option::{self, Some, None}.
  • std::result::Result::{self, Ok, Err}.
  • std::string::{String, ToString}.
  • std::vec::Vec.

如果你因为某些原因不想要这个prelude怎么办?就加属性#![no_implicit_prelude]。我们来试一试,看编译器的抱怨。

// ⚠️
#![no_implicit_prelude]
fn main() {
    let my_vec = vec![8, 9, 10];
    let my_string = String::from("This won't work");
    println!("{:?}, {}", my_vec, my_string);
}

现在Rust根本不知道你想做什么。

error: cannot find macro `println` in this scope
 --> src/main.rs:5:5
  |
5 |     println!("{:?}, {}", my_vec, my_string);
  |     ^^^^^^^

error: cannot find macro `vec` in this scope
 --> src/main.rs:3:18
  |
3 |     let my_vec = vec![8, 9, 10];
  |                  ^^^

error[E0433]: failed to resolve: use of undeclared type or module `String`
 --> src/main.rs:4:21
  |
4 |     let my_string = String::from("This won't work");
  |                     ^^^^^^ use of undeclared type or module `String`

error: aborting due to 3 previous errors

因此,对于这个简单的代码,你需要告诉Rust使用extern(外部)crate,叫做std,然后是你想要的元素。这里是我们要做的一切,只是为了创建一个Vec和一个String,并打印它。

#![no_implicit_prelude]

extern crate std; // Now you have to tell Rust that you want to use a crate called std
use std::vec; // We need the vec macro
use std::string::String; // and string
use std::convert::From; // and this to convert from a &str to the String
use std::println; // and this to print

fn main() {
    let my_vec = vec![8, 9, 10];
    let my_string = String::from("This won't work");
    println!("{:?}, {}", my_vec, my_string);
}

现在终于成功了,打印出[8, 9, 10], This won't work。所以你可以明白为什么Rust要用prelude了。但如果你愿意,你不需要使用它。而且你甚至可以使用#![no_std](我们曾经看到过),用于你连堆栈内存这种东西都用不上的时候。但大多数时候,你根本不用考虑不用prelude或std

那么为什么之前我们没有看到extern这个关键字呢?是因为你已经不需要它了。以前,当带入外部crate时,你必须使用它。所以以前要使用rand,你必须要写成:

#![allow(unused)]
fn main() {
extern crate rand;
}

然后用 use 语句来表示你想使用的修改、trait等。但现在Rust编译器已经不需要这些帮助了--你只需要使用use,rust就知道在哪里可以找到它。所以你几乎再也不需要extern crate了,但在其他人的Rust代码中,你可能仍然会在顶部看到它。

Time

std::time是你可以找到时间函数的地方。(如果你想要更多的功能,chrono这样的crate也可以。)最简单的功能就是用Instant::now()获取系统时间即可。

use std::time::Instant;

fn main() {
    let time = Instant::now();
    println!("{:?}", time);
}

如果你打印出来,你会得到这样的东西。Instant { tv_sec: 2738771, tv_nsec: 685628140 }. 这说的是秒和纳秒,但用处不大。比如你看2738771秒(写于8月),就是31.70天。这和月份、日子没有任何关系。但是Instant的页面告诉我们,它本身不应该有用。它说它是 "不透明的,只有和Duration一起才有用"。Opaque的意思是 "你搞不清楚",而Duration的意思是 "过了多少时间"。所以它只有在做比较时间这样的事情时才有用。

如果你看左边的trait,其中一个是Sub<Instant>。也就是说我们可以用-来减去一个。而当我们点击[src]看它的作用时,页面显示:

#![allow(unused)]
fn main() {
impl Sub<Instant> for Instant {
    type Output = Duration;

    fn sub(self, other: Instant) -> Duration {
        self.duration_since(other)
    }
}
}

因此,它需要一个Instant,并使用.duration_since()给出一个Duration。让我们试着打印一下。我们将创建两个相邻的 Instant::now(),然后让程序忙活一会儿,再创建一个 Instant::now()。然后我们再创建一个Instant::now(). 最后,我们来看看用了多长时间。

use std::time::Instant;

fn main() {
    let time1 = Instant::now();
    let time2 = Instant::now(); // These two are right next to each other

    let mut new_string = String::new();
    loop {
        new_string.push('წ'); // Make Rust push this Georgian letter onto the String
        if new_string.len() > 100_000 { //  until it is 100,000 bytes long
            break;
        }
    }
    let time3 = Instant::now();
    println!("{:?}", time2 - time1);
    println!("{:?}", time3 - time1);
}

这将打印出这样的东西。

1.025µs
683.378µs

所以,这只是1微秒多与683毫秒。我们可以看到,Rust确实花了一些时间来做。

不过我们可以用一个Instant做一件有趣的事情。 我们可以把它变成Stringformat!("{:?}", Instant::now());。它的样子是这样的:

use std::time::Instant;

fn main() {
    let time1 = format!("{:?}", Instant::now());
    println!("{}", time1);
}

这样就会打印出类似Instant { tv_sec: 2740773, tv_nsec: 632821036 }的东西。这是没有用的,但是如果我们使用 .iter().rev() 以及 .skip(2),我们可以跳过最后的 } 。我们可以用它来创建一个随机数发生器。

use std::time::Instant;

fn bad_random_number(digits: usize) {
    if digits > 9 {
        panic!("Random number can only be up to 9 digits");
    }
    let now = Instant::now();
    let output = format!("{:?}", now);

    output
        .chars()
        .rev()
        .skip(2)
        .take(digits)
        .for_each(|character| print!("{}", character));
    println!();
}

fn main() {
    bad_random_number(1);
    bad_random_number(1);
    bad_random_number(3);
    bad_random_number(3);
}

这样就会打印出类似这样的内容:

6
4
967
180

这个函数被称为bad_random_number,因为它不是一个很好的随机数生成器。Rust有更好的crate,可以用比rand更少的代码创建随机数,比如fastrand。但这是一个很好的例子,你可以利用你的想象力用Instant来做一些事情。

当你有一个线程时,你可以使用std::thread::sleep使它停止一段时间。当你这样做时,你必须给它一个duration。你不必创建多个线程来做这件事,因为每个程序至少在一个线程上。sleep虽然需要一个Duration,所以它可以知道要睡多久。你可以这样选单位:Duration::from_millis(), Duration::from_secs, 等等。这里举一个例子:

use std::time::Duration;
use std::thread::sleep;

fn main() {
    let three_seconds = Duration::from_secs(3);
    println!("I must sleep now.");
    sleep(three_seconds);
    println!("Did I miss anything?");
}

这将只打印

I must sleep now.
Did I miss anything?

但线程在三秒钟内什么也不做。当你有很多线程需要经常尝试一些事情时,比如连接,你通常会使用.sleep()。你不希望线程在一秒钟内使用你的处理器尝试10万次,而你只是想让它有时检查一下。所以,你就可以设置一个Duration,它就会在每次醒来的时候尝试做它的任务。

其他宏

我们再来看看其他一些宏。

unreachable!()

这个宏有点像todo!(),除了它是针对你永远不会用的代码。也许你在一个枚举中有一个match,你知道它永远不会选择其中的一个分支,所以代码永远无法达到那个分支。如果是这样,你可以写unreachable!(),这样编译器就知道可以忽略这部分。

例如,假设你有一个程序,当你选择一个地方居住时,它会写一些东西。在乌克兰,除了切尔诺贝利,其他地方都不错。你的程序不让任何人选择切尔诺贝利,因为它现在不是一个好地方。但是这个枚举是很早以前在别人的代码里做的,你无法更改。所以在match的分支中,你可以用这个宏。它是这样的:

enum UkrainePlaces {
    Kiev,
    Kharkiv,
    Chernobyl, // Pretend we can't change the enum - Chernobyl will always be here
    Odesa,
    Dnipro,
}

fn choose_city(place: &UkrainePlaces) {
    use UkrainePlaces::*;
    match place {
        Kiev => println!("You will live in Kiev"),
        Kharkiv => println!("You will live in Kharkiv"),
        Chernobyl => unreachable!(),
        Odesa => println!("You will live in Odesa"),
        Dnipro => println!("You will live in Dnipro"),
    }
}

fn main() {
    let user_input = UkrainePlaces::Kiev; // Pretend the user input is made from some other function. The user can't choose Chernobyl, no matter what
    choose_city(&user_input);
}

这将打印出 You will live in Kiev

unreachable!()对你来说也很好读,因为它提醒你代码的某些部分是不可访问的。不过你必须确定代码确实是不可访问的。如果编译器调用unreachable!(),程序就会崩溃。

此外,如果你曾经有不可达的代码,而编译器知道,它会告诉你。下面是一个简单的例子:

fn main() {
    let true_or_false = true;

    match true_or_false {
        true => println!("It's true"),
        false => println!("It's false"),
        true => println!("It's true"), // Whoops, we wrote true again
    }
}

它会说

warning: unreachable pattern
 --> src/main.rs:7:9
  |
7 |         true => println!("It's true"),
  |         ^^^^
  |

但是unreachable!()是用于编译器无法知道的时候,就像我们另一个例子。

column!, line!, file!, module_path!

这四个宏有点像dbg!(),因为你只是把它们放进代码去给你调试信息。但是它们不需要任何变量--你只需要用它们和括号一起使用,而没有其他的东西。它们放到一起很容易学:

  • column!()给你写的那一列
  • file!()给你写的文件的名称
  • line!()给你写的那行字,然后是
  • module_path!()给你模块的位置。

接下来的代码在一个简单的例子中展示了这三者。我们将假装有更多的代码(mod里面的mod),因为这就是我们要使用这些宏的原因。你可以想象一个大的Rust程序,它有许多mod和文件。

pub mod something {
    pub mod third_mod {
        pub fn print_a_country(input: &mut Vec<&str>) {
            println!(
                "The last country is {} inside the module {}",
                input.pop().unwrap(),
                module_path!()
            );
        }
    }
}

fn main() {
    use something::third_mod::*;
    let mut country_vec = vec!["Portugal", "Czechia", "Finland"];

    // do some stuff
    println!("Hello from file {}", file!());

    // do some stuff
    println!(
        "On line {} we got the country {}",
        line!(),
        country_vec.pop().unwrap()
    );

    // do some more stuff

    println!(
        "The next country is {} on line {} and column {}.",
        country_vec.pop().unwrap(),
        line!(),
        column!(),
    );

    // lots more code

    print_a_country(&mut country_vec);
}

它打印的是这样的。

Hello from file src/main.rs
On line 23 we got the country Finland
The next country is Czechia on line 32 and column 9.
The last country is Portugal inside the module rust_book::something::third_mod

cfg!

我们知道,你可以使用 #[cfg(test)]#[cfg(windows)] 这样的属性来告诉编译器在某些情况下该怎么做。当你有test时,当你在测试模式下运行Rust时,它会运行代码(如果是在电脑上,你输入cargo test)。而当你使用windows时,如果用户使用的是Windows,它就会运行代码。但也许你只是想根据不同操作系统对依赖系统的代码做很小的修改。这时候这个宏就很有用了。它返回一个bool

fn main() {
    let helpful_message = if cfg!(target_os = "windows") { "backslash" } else { "slash" };

    println!(
        "...then in your hard drive, type the directory name followed by a {}. Then you...",
        helpful_message
    );
}

这将以不同的方式打印,取决于你的系统。Rust Playground在Linux上运行,所以会打印:

...then in your hard drive, type the directory name followed by a slash. Then you...

cfg!()适用于任何一种配置。下面是一个例子,当你在测试中使用一个函数时,它的运行方式会有所不同。

#[cfg(test)] // cfg! will know to look for the word test
mod testing {
    use super::*;
    #[test]
    fn check_if_five() {
        assert_eq!(bring_number(true), 5); // This bring_number() function should return 5
    }
}

fn bring_number(should_run: bool) -> u32 { // This function takes a bool as to whether it should run
    if cfg!(test) && should_run { // if it should run and has the configuration test, return 5
        5
    } else if should_run { // if it's not a test but it should run, print something. When you run a test it ignores println! statements
        println!("Returning 5. This is not a test");
        5
    } else {
        println!("This shouldn't run, returning 0."); // otherwise return 0
        0
    }
}

fn main() {
    bring_number(true);
    bring_number(false);
}

现在根据配置的不同,它的运行方式也会不同。如果你只是运行程序,它会给你这样的结果:

Returning 5. This is not a test
This shouldn't run, returning 0.

但如果你在测试模式下运行它(cargo test,用于电脑上的Rust),它实际上会运行测试。因为在这种情况下,测试总是返回5,所以它会通过。

running 1 test
test testing::check_if_five ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out