半小时学 Rust

为了提高编程语言的流利程度,必须阅读大量内容。但是如果你不知道它的意思,你怎么能读到很多呢?

在本文中,我不会专注于一两个概念,而是尽可能多地浏览 Rust 片段,并解释它们包含的关键字和符号的含义。

好的,让我们开始吧

let引入变量绑定

let x; // declare "x"
x = 42; // assign 42 to "x"

也可以向下面一样写成一行

let x = 42;

可以使用 明确指定变量的类型:,这是一个类型注释:

let x: i32; // `i32` is a signed 32-bit integer
x = 42;

// there's i8, i16, i32, i64, i128
//    also u8, u16, u32, u64, u128 for unsigned

也可以向下面一样写成一行

let x: i32 = 42;

如果您声明一个名称并稍后对其进行初始化,编译器将阻止您在初始化之前使用它。

let x;
foobar(x); // error: borrow of possibly-uninitialized variable: `x`
x = 42;

但是,下面这种做法完全没问题:

let x;
x = 42;
foobar(x); // the type of `x` will be inferred from here

下划线_是一个特殊的名称——或者更确切地说,是“缺少名称”。它基本上意味着扔掉一些东西:

// this does *nothing* because 42 is a constant
let _ = 42;

// this calls `get_thing` but throws away its result
let _ = get_thing();

以下划线开头的名称是常规名称,只是编译器不会警告它们未使用:

// we may use `_x` eventually, but our code is a work-in-progress
// and we just wanted to get rid of a compiler warning for now.
let _x = 42;

可以引入具有相同名称的单独绑定 - 您可以隐藏 变量绑定:

let x = 13;
let x = x + 3;
// using `x` after that line only refers to the second `x`,
// the first `x` no longer exists.

Rust 有元组,您可以将其视为“不同类型值的固定长度集合”。

let pair = ('a', 17);
pair.0; // this is 'a'
pair.1; // this is 17

如果我们真的想注释 的类型pair,我们会写:

let pair: (char, i32) = ('a', 17);

元组在进行赋值时可以被解构,这意味着它们被分解成各自的字段:

let (some_char, some_int) = ('a', 17);
// now, `some_char` is 'a', and `some_int` is 17

这在函数返回元组时特别有用:

let (left, right) = slice.split_at(middle);

当然,当解构一个元组时,_可以用来丢弃它的一部分:

let (_, right) = slice.split_at(middle);

分号表示语句的结束:

let x = 3;
let y = 5;
let z = y + x;

这意味着语句可以跨越多行:

let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
    .iter()
    .map(|x| x + 3)
    .fold(0, |x, y| x + y);

(我们稍后会讨论这些实际上是什么意思)。

fn声明一个函数。

这是一个空函数:

fn greet() {
    println!("Hi there!");
}

这是一个返回 32 位有符号整数的函数。箭头表示其返回类型:

fn fair_dice_roll() -> i32 {
    4
}

一对括号声明了一个块,它有自己的范围:

// This prints "in", then "out"
fn main() {
    let x = "out";
    {
        // this is a different `x`
        let x = "in";
        println!("{}", x);
    }
    println!("{}", x);
}

块也是表达式,这意味着它们评估为.. 一个值。

// this:
let x = 42;

// is equivalent to this:
let x = { 42 };

在一个块内,可以有多个语句:

let x = {
    let y = 1; // first statement
    let z = 2; // second statement
    y + z // this is the *tail* - what the whole block will evaluate to
};

这就是为什么“在函数末尾省略分号”与返回相同,即。这些是等效的:

fn fair_dice_roll() -> i32 {
    return 4;
}

fn fair_dice_roll() -> i32 {
    4
}

if条件也是表达式:

fn fair_dice_roll() -> i32 {
    if feeling_lucky {
        6
    } else {
        4
    }
}

Amatch也是一个表达式:

fn fair_dice_roll() -> i32 {
    match feeling_lucky {
        true => 6,
        false => 4,
    }
}

点通常用于访问值的字段:

let a = (10, 20);
a.0; // this is 10

let amos = get_some_struct();
amos.nickname; // this is "fasterthanlime"

或者调用一个值的方法:

let nick = "fasterthanlime";
nick.len(); // this is 14

双冒号 ,::类似,但它在命名空间上运行。

在这个例子中,std是一个板条箱(~一个库),cmp一个模块 (~一个源文件),并且min是一个函数:

let least = std::cmp::min(3, 8); // this is 3

use指令可用于从其他命名空间“引入范围”名称:

use std::cmp::min;
let least = min(7, 1); // this is 1

在use指令中,大括号还有另一个含义:它们是“globs”。如果我们想同时导入minand max,我们可以执行以下任何操作:

// this works:
use std::cmp::min;
use std::cmp::max;

// this also works:
use std::cmp::{min, max};

// this also works!
use std::{cmp::min, cmp::max};

通配符 ( *) 允许您从命名空间导入每个符号:

// this brings `min` and `max` in scope, and many other things
use std::cmp::*;

类型也是命名空间,方法可以作为常规函数调用:

let x = "amos".len(); // this is 4
let x = str::len("amos"); // this is also 4

str是原始类型,但默认情况下许多非原始类型也在范围内。

// `Vec` is a regular struct, not a primitive type
let v = Vec::new();

// this is exactly the same code, but with the *full* path to `Vec`
let v = std::vec::Vec::new();

这是可行的,因为 Rust 在每个模块的开头插入了这个:

use std::prelude::v1::*;

(这反过来又重新导出了许多符号,例如、Vec和String)。OptionResult

结构体是用struct关键字声明的:

struct Vec2 {
    x: f64, // 64-bit floating point, aka "double precision"
    y: f64,
}

它们可以使用struct literals进行初始化:

let v1 = Vec2 { x: 1.0, y: 3.0 };
let v2 = Vec2 { y: 2.0, x: 4.0 };
// the order does not matter, only the names do

有一个从另一个结构初始化其余字段的快捷方式:

let v3 = Vec2 {
    x: 14.0,
    ..v2
};

这称为“结构更新语法”,只能发生在最后一个位置,并且不能跟逗号。

请注意,其余字段可能意味着所有字段:

let v4 = Vec2 { ..v3 };

像元组一样,结构可以被解构。就像这是一个有效的let模式:

let (left, right) = slice.split_at(middle);

这是这样的:

let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` is now 3.0, `y` is now `6.0`

还有这个:

let Vec2 { x, .. } = v;
// this throws away `v.y`

let模式可以用作条件if:

struct Number {
    odd: bool,
    value: i32,
}

fn main() {
    let one = Number { odd: true, value: 1 };
    let two = Number { odd: false, value: 2 };
    print_number(one);
    print_number(two);
}

fn print_number(n: Number) {
    if let Number { odd: true, value } = n {
        println!("Odd number: {}", value);
    } else if let Number { odd: false, value } = n {
        println!("Even number: {}", value);
    }
}

// this prints:
// Odd number: 1
// Even number: 2

match手臂也是模型,就像if let:

fn print_number(n: Number) {
    match n {
        Number { odd: true, value } => println!("Odd number: {}", value),
        Number { odd: false, value } => println!("Even number: {}", value),
    }
}

// this prints the same as before

Amatch必须是详尽的:至少一个手臂需要匹配。

fn print_number(n: Number) {
    match n {
        Number { value: 1, .. } => println!("One"),
        Number { value: 2, .. } => println!("Two"),
        Number { value, .. } => println!("{}", value),
        // if that last arm didn't exist, we would get a compile-time error
    }
}

如果这很难,_可以用作“包罗万象”的模式:

fn print_number(n: Number) {
    match n.value {
        1 => println!("One"),
        2 => println!("Two"),
        _ => println!("{}", n.value),
    }
}

您可以在自己的类型上声明方法:

struct Number {
    odd: bool,
    value: i32,
}

impl Number {
    fn is_strictly_positive(self) -> bool {
        self.value > 0
    }
}

并像往常一样使用它们:

fn main() {
    let minus_two = Number {
        odd: false,
        value: -2,
    };
    println!("positive? {}", minus_two.is_strictly_positive());
    // this prints "positive? false"
}

变量绑定默认是不可变的,这意味着它们的内部不能被改变:

fn main() {
    let n = Number {
        odd: true,
        value: 17,
    };
    n.odd = false; // error: cannot assign to `n.odd`,
                   // as `n` is not declared to be mutable
}

而且他们不能被分配给:

fn main() {
    let n = Number {
        odd: true,
        value: 17,
    };
    n = Number {
        odd: false,
        value: 22,
    }; // error: cannot assign twice to immutable variable `n`
}

mut使变量绑定可变:

fn main() {
    let mut n = Number {
        odd: true,
        value: 17,
    }
    n.value = 19; // all good
}

特征是多种类型的共同点:

trait Signed {
    fn is_strictly_negative(self) -> bool;
}

您可以实现:

您对任何人的类型的特征之一 任何人对你的一种类型的特征 但不是外来类型的外来特征 这些被称为“孤儿规则”。

这是我们的类型的特征的实现:

impl Signed for Number {
    fn is_strictly_negative(self) -> bool {
        self.value < 0
    }
}

fn main() {
    let n = Number { odd: false, value: -44 };
    println!("{}", n.is_strictly_negative()); // prints "true"
}

我们在外来类型(甚至是原始类型)上的特征:

impl Signed for i32 {
    fn is_strictly_negative(self) -> bool {
        self < 0
    }
}

fn main() {
    let n: i32 = -44;
    println!("{}", n.is_strictly_negative()); // prints "true"
}

我们类型的一个外来特征:

// the `Neg` trait is used to overload `-`, the
// unary minus operator.
impl std::ops::Neg for Number {
    type Output = Number;

    fn neg(self) -> Number {
        Number {
            value: -self.value,
            odd: self.odd,
        }        
    }
}

fn main() {
    let n = Number { odd: true, value: 987 };
    let m = -n; // this is only possible because we implemented `Neg`
    println!("{}", m.value); // prints "-987"
}

一个impl块总是代表一个类型,所以,在那个块里面,Self 意味着那个类型:

impl std::ops::Neg for Number {
    type Output = Self;

    fn neg(self) -> Self {
        Self {
            value: -self.value,
            odd: self.odd,
        }        
    }
}

有些特征是标记——它们不是说一个类型实现了一些方法,而是说某些事情可以用一个类型来完成。

例如,i32实现 trait Copy(简而言之,i32 is Copy),所以这有效:

fn main() {
    let a: i32 = 15;
    let b = a; // `a` is copied
    let c = a; // `a` is copied again
}

这也有效:

fn print_i32(x: i32) {
    println!("x = {}", x);
}

fn main() {
    let a: i32 = 15;
    print_i32(a); // `a` is copied
    print_i32(a); // `a` is copied again
}

但是Number结构不是Copy,所以这不起作用:

fn main() {
    let n = Number { odd: true, value: 51 };
    let m = n; // `n` is moved into `m`
    let o = n; // error: use of moved value: `n`
}

这也不是:

fn print_number(n: Number) {
    println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}

fn main() {
    let n = Number { odd: true, value: 51 };
    print_number(n); // `n` is moved
    print_number(n); // error: use of moved value: `n`
}

但如果print_number取而代之的是不可变引用,它会起作用:

fn print_number(n: &Number) {
    println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}

fn main() {
    let n = Number { odd: true, value: 51 };
    print_number(&n); // `n` is borrowed for the time of the call
    print_number(&n); // `n` is borrowed again
}

如果函数采用可变引用,它也可以工作——但前提是我们的变量绑定也是mut.

fn invert(n: &mut Number) {
    n.value = -n.value;
}

fn print_number(n: &Number) {
    println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}

fn main() {
    // this time, `n` is mutable
    let mut n = Number { odd: true, value: 51 };
    print_number(&n);
    invert(&mut n); // `n is borrowed mutably - everything is explicit
    print_number(&n);
}

Trait 方法也可以self通过引用或可变引用来获取:

impl std::clone::Clone for Number {
    fn clone(&self) -> Self {
        Self { ..*self }
    }
}

当调用 trait 方法时,接收者被隐式借用:

fn main() {
    let n = Number { odd: true, value: 51 };
    let mut m = n.clone();
    m.value += 100;

    print_number(&n);
    print_number(&m);
}

为了强调这一点:这些是等效的:

let m = n.clone();

let m = std::clone::Clone::clone(&n);

Copy像没有方法的标记特征:

// note: `Copy` requires that `Clone` is implemented too
impl std::clone::Clone for Number {
    fn clone(&self) -> Self {
        Self { ..*self }
    }
}

impl std::marker::Copy for Number {}

现在,Clone仍然可以使用:

fn main() {
    let n = Number { odd: true, value: 51 };
    let m = n.clone();
    let o = n.clone();
}

但Number值将不再移动:

fn main() {
    let n = Number { odd: true, value: 51 };
    let m = n; // `m` is a copy of `n`
    let o = n; // same. `n` is neither moved nor borrowed.
}

有些特质很常见,可以通过使用derive属性自动实现:

#[derive(Clone, Copy)]
struct Number {
    odd: bool,
    value: i32,
}

// this expands to `impl Clone for Number` and `impl Copy for Number` blocks.

函数可以是通用的:

fn foobar<T>(arg: T) {
    // do something with `arg`
}

它们可以有多个类型参数,然后可以在函数的声明及其主体中使用,而不是具体类型:

fn foobar<L, R>(left: L, right: R) {
    // do something with `left` and `right`
}

类型参数通常有约束,所以你实际上可以用它们做一些事情。

最简单的约束只是特征名称:

fn print<T: Display>(value: T) {
    println!("value = {}", value);
}

fn print<T: Debug>(value: T) {
    println!("value = {:?}", value);
}

类型参数约束有更长的语法:

fn print<T>(value: T)
where
    T: Display,
{
    println!("value = {}", value);
}

约束可能更复杂:它们可能需要一个类型参数来实现多个特征:

use std::fmt::Debug;

fn compare<T>(left: T, right: T)
where
    T: Debug + PartialEq,
{
    println!("{:?} {} {:?}", left, if left == right { "==" } else { "!=" }, right);
}

fn main() {
    compare("tea", "coffee");
    // prints: "tea" != "coffee"
}

泛型函数可以被认为是命名空间,包含具有不同具体类型的无限函数。

与板条箱、模块和类型一样,通用函数可以使用“探索”(导航?)::

fn main() {
    use std::any::type_name;
    println!("{}", type_name::<i32>()); // prints "i32"
    println!("{}", type_name::<(f64, char)>()); // prints "(f64, char)"
}

这被亲切地称为turbofish 语法,因为 ::<>它看起来像一条鱼。

结构也可以是通用的:

struct Pair<T> {
    a: T,
    b: T,
}

fn print_type_name<T>(_val: &T) {
    println!("{}", std::any::type_name::<T>());
}

fn main() {
    let p1 = Pair { a: 3, b: 9 };
    let p2 = Pair { a: true, b: false };
    print_type_name(&p1); // prints "Pair<i32>"
    print_type_name(&p2); // prints "Pair<bool>"
}

标准库类型Vec(〜堆分配的数组)是通用的:

fn main() {
    let mut v1 = Vec::new();
    v1.push(1);
    let mut v2 = Vec::new();
    v2.push(false);
    print_type_name(&v1); // prints "Vec<i32>"
    print_type_name(&v2); // prints "Vec<bool>"
}

说到Vec,它带有一个或多或少给出“vec 文字”的宏:

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![true, false, true];
    print_type_name(&v1); // prints "Vec<i32>"
    print_type_name(&v2); // prints "Vec<bool>"
}

全部name!(),name![]或name!{}调用宏。宏只是扩展为常规代码。

其实println就是一个宏:

fn main() {
    println!("{}", "Hello there!");
}

这扩展为具有与以下效果相同的内容:

fn main() {
    use std::io::{self, Write};
    io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}

panic也是一个宏。如果启用,它会猛烈停止执行并显示错误消息以及错误的文件名/行号:

fn main() {
    panic!("This panics");
}
// output: thread 'main' panicked at 'This panics', src/main.rs:3:5

一些方法也恐慌。例如,Option类型可以包含某些内容,也可以不包含任何内容。如果.unwrap()调用它,它什么都不包含,它会恐慌:

fn main() {
    let o1: Option<i32> = Some(128);
    o1.unwrap(); // this is fine

    let o2: Option<i32> = None;
    o2.unwrap(); // this panics!
}

// output: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21

Option不是结构 - 它是一个enum, 有两个变体。

enum Option<T> {
    None,
    Some(T),
}

impl<T> Option<T> {
    fn unwrap(self) -> T {
        // enums variants can be used in patterns:
        match self {
            Self::Some(t) => t,
            Self::None => panic!(".unwrap() called on a None option"),
        }
    }
}

use self::Option::{None, Some};

fn main() {
    let o1: Option<i32> = Some(128);
    o1.unwrap(); // this is fine

    let o2: Option<i32> = None;
    o2.unwrap(); // this panics!
}

// output: thread 'main' panicked at '.unwrap() called on a None option', src/main.rs:11:27

Result也是一个枚举,它可以包含某些内容或错误:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

它在展开并包含错误时也会发生恐慌。

变量绑定有一个“生命周期”:

fn main() {
    // `x` doesn't exist yet
    {
        let x = 42; // `x` starts existing
        println!("x = {}", x);
        // `x` stops existing
    }
    // `x` no longer exists
}

同样,引用也有生命周期:

fn main() {
    // `x` doesn't exist yet
    {
        let x = 42; // `x` starts existing
        let x_ref = &x; // `x_ref` starts existing - it borrows `x`
        println!("x_ref = {}", x_ref);
        // `x_ref` stops existing
        // `x` stops existing
    }
    // `x` no longer exists
}

引用的生命周期不能超过它借用的变量绑定的生命周期:

fn main() {
    let x_ref = {
        let x = 42;
        &x
    };
    println!("x_ref = {}", x_ref);
    // error: `x` does not live long enough
}

变量绑定可以不可变地多次借用:

fn main() {
    let x = 42;
    let x_ref1 = &x;
    let x_ref2 = &x;
    let x_ref3 = &x;
    println!("{} {} {}", x_ref1, x_ref2, x_ref3);
}

借用时,变量绑定不能被改变:

fn main() {
    let mut x = 42;
    let x_ref = &x;
    x = 13;
    println!("x_ref = {}", x_ref);
    // error: cannot assign to `x` because it is borrowed
}

虽然不可变地借用,但变量不能可变地借用:

fn main() {
    let mut x = 42;
    let x_ref1 = &x;
    let x_ref2 = &mut x;
    // error: cannot borrow `x` as mutable because it is also borrowed as immutable
    println!("x_ref1 = {}", x_ref1);
}

函数参数中的引用也有生命周期:

fn print(x: &i32) {
    // `x` is borrowed (from the outside) for the
    // entire time this function is called.
}

可以使用具有不同生命周期的借用来调用具有引用参数的函数,因此:

所有接受引用的函数都是通用的 生命周期是通用参数 Lifetimes 的名称以单引号开头,':

// elided (non-named) lifetimes:
fn print(x: &i32) {}

// named lifetimes:
fn print<'a>(x: &'a i32) {}

这允许返回其生命周期取决于参数的生命周期的引用:

struct Number {
    value: i32,
}

fn number_value<'a>(num: &'a Number) -> &'a i32 {
    &num.value
}

fn main() {
    let n = Number { value: 47 };
    let v = number_value(&n);
    // `v` borrows `n` (immutably), thus: `v` cannot outlive `n`.
    // While `v` exists, `n` cannot be mutably borrowed, mutated, moved, etc.
}

当有单个输入生命周期时,不需要命名,所有的生命周期都是相同的,所以下面两个函数是等价的:

fn number_value<'a>(num: &'a Number) -> &'a i32 {
    &num.value
}

fn number_value(num: &Number) -> &i32 {
    &num.value
}

结构也可以在生命周期中是通用的,这允许它们保存引用:

struct NumRef<'a> {
    x: &'a i32,
}

fn main() {
    let x: i32 = 99;
    let x_ref = NumRef { x: &x };
    // `x_ref` cannot outlive `x`, etc.
}

相同的代码,但有一个附加功能:

struct NumRef<'a> {
    x: &'a i32,
}

fn as_num_ref<'a>(x: &'a i32) -> NumRef<'a> {
    NumRef { x: &x }
}

fn main() {
    let x: i32 = 99;
    let x_ref = as_num_ref(&x);
    // `x_ref` cannot outlive `x`, etc.
}

相同的代码,但具有“省略”的生命周期:

struct NumRef<'a> {
    x: &'a i32,
}

fn as_num_ref(x: &i32) -> NumRef<'_> {
    NumRef { x: &x }
}

fn main() {
    let x: i32 = 99;
    let x_ref = as_num_ref(&x);
    // `x_ref` cannot outlive `x`, etc.
}

impl块也可以在生命周期内通用:

impl<'a> NumRef<'a> {
    fn as_i32_ref(&'a self) -> &'a i32 {
        self.x
    }
}

fn main() {
    let x: i32 = 99;
    let x_num_ref = NumRef { x: &x };
    let x_i32_ref = x_num_ref.as_i32_ref();
    // neither ref can outlive `x`
}

但是你也可以在那里做省略(“省略”):

impl<'a> NumRef<'a> {
    fn as_i32_ref(&self) -> &i32 {
        self.x
    }
}

如果你不需要名字,你可以更努力地省略:

impl NumRef<'_> {
    fn as_i32_ref(&self) -> &i32 {
        self.x
    }
}

有一个特殊的生命周期,名为'static,它在整个程序的生命周期内都有效。

字符串文字是'static:

struct Person {
    name: &'static str,
}

fn main() {
    let p = Person {
        name: "fasterthanlime",
    };
}

但拥有的字符串不是静态的:

struct Person {
    name: &'static str,
}

fn main() {
    let name = format!("fasterthan{}", "lime");
    let p = Person { name: &name };
    // error: `name` does not live long enough
}

在最后一个示例中,本地name不是 a &'static str,而是 a String。它是动态分配的,它将被释放。它的生命周期小于整个程序(即使它恰好在main)。

要在 中存储非'static字符串Person,它需要:

A)终生通用:

struct Person<'a> {
    name: &'a str,
}

fn main() {
    let name = format!("fasterthan{}", "lime");
    let p = Person { name: &name };
    // `p` cannot outlive `name`
}

或者

B)取得字符串的所有权

struct Person {
    name: String,
}

fn main() {
    let name = format!("fasterthan{}", "lime");
    let p = Person { name: name };
    // `name` was moved into `p`, their lifetimes are no longer tied.
}

说到:在结构文字中,当一个字段设置为同名的变量绑定时:

 let p = Person { name: name };

可以这样缩短:

let p = Person { name };

对于 Rust 中的许多类型,有拥有和非拥有的变体:

字符串:String是拥有的,&str是一个引用 路径:PathBuf是拥有的,&Path是一个参考 Collections:Vec是拥有的,&[T]是一个参考 Rust 有切片——它们是对多个连续元素的引用。

您可以借用向量的一部分,例如:

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    let v2 = &v[2..4];
    println!("v2 = {:?}", v2);
}

// output:
// v2 = [3, 4]

以上并不神奇。索引运算符 ( foo[index]) 被IndexandIndexMut特征重载。

..语法只是范围文字。范围只是标准库中定义的几个结构。

它们可以是开放式的,并且它们最右边的边界可以是包容性的,如果它前面有=.

fn main() {
    // 0 or greater
    println!("{:?}", (0..).contains(&100)); // true
    // strictly less than 20
    println!("{:?}", (..20).contains(&20)); // false
    // 20 or less than 20
    println!("{:?}", (..=20).contains(&20)); // true
    // only 3, 4, 5
    println!("{:?}", (3..6).contains(&4)); // true
}

借用规则适用于切片。

fn tail(s: &[u8]) -> &[u8] {
  &s[1..] 
}

fn main() {
    let x = &[1, 2, 3, 4, 5];
    let y = tail(x);
    println!("y = {:?}", y);
}

这与以下内容相同:

fn tail<'a>(s: &'a [u8]) -> &'a [u8] {
  &s[1..] 
}

这是合法的:

fn main() {
    let y = {
        let x = &[1, 2, 3, 4, 5];
        tail(x)
    };
    println!("y = {:?}", y);
}

...但只是因为[1, 2, 3, 4, 5]是一个'static数组。

所以,这是非法的:

fn main() {
    let y = {
        let v = vec![1, 2, 3, 4, 5];
        tail(&v)
        // error: `v` does not live long enough
    };
    println!("y = {:?}", y);
}

...因为向量是堆分配的,并且它具有非'static生命周期。

&str值实际上是切片。

fn file_ext(name: &str) -> Option<&str> {
    // this does not create a new string - it returns
    // a slice of the argument.
    name.split(".").last()
}

fn main() {
    let name = "Read me. Or don't.txt";
    if let Some(ext) = file_ext(name) {
        println!("file extension: {}", ext);
    } else {
        println!("no file extension");
    }
}

...所以借用规则也适用于此:

fn main() {
    let ext = {
        let name = String::from("Read me. Or don't.txt");
        file_ext(&name).unwrap_or("")
        // error: `name` does not live long enough
    };
    println!("extension: {:?}", ext);
}

可能失败的函数通常会返回Result:

fn main() {
    let s = std::str::from_utf8(&[240, 159, 141, 137]);
    println!("{:?}", s);
    // prints: Ok("🍉")

    let s = std::str::from_utf8(&[195, 40]);
    println!("{:?}", s);
    // prints: Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
}

如果你想在失败的情况下panic,你可以.unwrap():

fn main() {
    let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
    println!("{:?}", s);
    // prints: "🍉"

    let s = std::str::from_utf8(&[195, 40]).unwrap();
    // prints: thread 'main' panicked at 'called `Result::unwrap()`
    // on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }',
    // src/libcore/result.rs:1165:5
}

或者.expect(),对于自定义消息:

fn main() {
    let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
    // prints: thread 'main' panicked at 'valid utf-8: Utf8Error
    // { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
}

或者,您可以match:

fn main() {
    match std::str::from_utf8(&[240, 159, 141, 137]) {
        Ok(s) => println!("{}", s),
        Err(e) => panic!(e),
    }
    // prints 🍉
}

或者你可以if let:

fn main() {
    if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
        println!("{}", s);
    }
    // prints 🍉
}

或者你可以冒泡错误:

fn main() -> Result<(), std::str::Utf8Error> {
    match std::str::from_utf8(&[240, 159, 141, 137]) {
        Ok(s) => println!("{}", s),
        Err(e) => return Err(e),
    }
    Ok(())
}

或者你可以用?简洁的方式来做到这一点:

fn main() -> Result<(), std::str::Utf8Error> {
    let s = std::str::from_utf8(&[240, 159, 141, 137])?;
    println!("{}", s);
    Ok(())
}

运算符可*用于取消引用,但您不需要这样做来访问字段或调用方法:

struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p = Point { x: 1.0, y: 3.0 };
    let p_ref = &p;
    println!("({}, {})", p_ref.x, p_ref.y);
}

// prints `(1, 3)`

如果类型是,你只能这样做Copy:

struct Point {
    x: f64,
    y: f64,
}

fn negate(p: Point) -> Point {
    Point {
        x: -p.x,
        y: -p.y,
    }
}

fn main() {
    let p = Point { x: 1.0, y: 3.0 };
    let p_ref = &p;
    negate(*p_ref);
    // error: cannot move out of `*p_ref` which is behind a shared reference
}
// now `Point` is `Copy`
#[derive(Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}

fn negate(p: Point) -> Point {
    Point {
        x: -p.x,
        y: -p.y,
    }
}

fn main() {
    let p = Point { x: 1.0, y: 3.0 };
    let p_ref = &p;
    negate(*p_ref); // ...and now this works
}

闭包只是 type 的函数Fn,FnMut或者FnOnce带有一些捕获的上下文。

它们的参数是一对管道 ( |) 中以逗号分隔的名称列表。它们不需要花括号,除非您想拥有多个语句。

fn for_each_planet<F>(f: F)
    where F: Fn(&'static str)
{
    f("Earth");
    f("Mars");
    f("Jupiter");
}

fn main() {
    for_each_planet(|planet| println!("Hello, {}", planet));
}

// prints:
// Hello, Earth
// Hello, Mars
// Hello, Jupiter

借用规则也适用于它们:

fn for_each_planet<F>(f: F)
    where F: Fn(&'static str)
{
    f("Earth");
    f("Mars");
    f("Jupiter");
}

fn main() {
    let greeting = String::from("Good to see you");
    for_each_planet(|planet| println!("{}, {}", greeting, planet));
    // our closure borrows `greeting`, so it cannot outlive it
}

例如,这将不起作用:

fn for_each_planet<F>(f: F)
    where F: Fn(&'static str) + 'static // `F` must now have "'static" lifetime
{
    f("Earth");
    f("Mars");
    f("Jupiter");
}

fn main() {
    let greeting = String::from("Good to see you");
    for_each_planet(|planet| println!("{}, {}", greeting, planet));
    // error: closure may outlive the current function, but it borrows
    // `greeting`, which is owned by the current function
}

但这会:

fn main() {
    let greeting = String::from("You're doing great");
    for_each_planet(move |planet| println!("{}, {}", greeting, planet));
    // `greeting` is no longer borrowed, it is *moved* into
    // the closure.
}

AnFnMut需要可变借用才能被调用,因此一次只能调用一次。

这是合法的:

fn foobar<F>(f: F)
    where F: Fn(i32) -> i32
{
    println!("{}", f(f(2))); 
}

fn main() {
    foobar(|x| x * 2);
}

// output: 8

这不是:

fn foobar<F>(mut f: F)
    where F: FnMut(i32) -> i32
{
    println!("{}", f(f(2))); 
    // error: cannot borrow `f` as mutable more than once at a time
}

fn main() {
    foobar(|x| x * 2);
}

这又是合法的:

fn foobar<F>(mut f: F)
    where F: FnMut(i32) -> i32
{
    let tmp = f(2);
    println!("{}", f(tmp)); 
}

fn main() {
    foobar(|x| x * 2);
}

// output: 8

FnMut存在是因为一些闭包可变地借用局部变量:

fn foobar<F>(mut f: F)
    where F: FnMut(i32) -> i32
{
    let tmp = f(2);
    println!("{}", f(tmp)); 
}

fn main() {
    let mut acc = 2;
    foobar(|x| {
        acc += 1;
        x * acc
    });
}

// output: 24

这些闭包不能传递给期望的函数Fn:

fn foobar<F>(f: F)
    where F: Fn(i32) -> i32
{
    println!("{}", f(f(2))); 
}

fn main() {
    let mut acc = 2;
    foobar(|x| {
        acc += 1;
        // error: cannot assign to `acc`, as it is a
        // captured variable in a `Fn` closure.
        // the compiler suggests "changing foobar
        // to accept closures that implement `FnMut`"
        x * acc
    });
}

FnOnce闭包只能被调用一次。它们的存在是因为一些闭包移出了捕获时已移动的变量:

fn foobar<F>(f: F)
    where F: FnOnce() -> String
{
    println!("{}", f()); 
}

fn main() {
    let s = String::from("alright");
    foobar(move || s);
    // `s` was moved into our closure, and our
    // closures moves it to the caller by returning
    // it. Remember that `String` is not `Copy`.
}

这是很自然的,因为 FnOnce 闭包需要移动才能被调用。

下面这个例子又不合法

fn foobar<F>(f: F)
    where F: FnOnce() -> String
{
    println!("{}", f()); 
    println!("{}", f()); 
    // error: use of moved value: `f`
}

而且,如果你需要说服我们的闭包确实会移动 s,这也是非法的:

fn main() {
    let s = String::from("alright");
    foobar(move || s);
    foobar(move || s);
    // use of moved value: `s`
}

但是这又是可以的:

fn main() {
    let s = String::from("alright");
    foobar(|| s.clone());
    foobar(|| s.clone());
}

这是一个带有两个参数的闭包:

fn foobar<F>(x: i32, y: i32, is_greater: F)
    where F: Fn(i32, i32) -> bool
{
    let (greater, smaller) = if is_greater(x, y) {
        (x, y)
    } else {
        (y, x)
    };
    println!("{} is greater than {}", greater, smaller);
}

fn main() {
    foobar(32, 64, |x, y| x > y);
}

这是一个忽略它的两个参数的闭包:

fn main() {
    foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}

这是一个有点令人担忧的关闭:

fn countdown<F>(count: usize, tick: F)
    where F: Fn(usize)
{
    for i in (1..=count).rev() {
        tick(i);
    }
}

fn main() {
    countdown(3, |i| println!("tick {}...", i));
}

// output:
// tick 3...
// tick 2...
// tick 1...

这是一个厕所关闭:

fn main() {
    countdown(3, |_| ());
}

之所以这样称呼是因为 |_| () 看起来像一个厕所。

任何可迭代的东西都可以在 for in 循环中使用。

我们刚刚看到使用了一个范围,但它也适用于 Vec:

fn main() {
    for i in vec![52, 49, 21] {
        println!("I like the number {}", i);
    }
}

或者切片:

fn main() {
    for i in &[52, 49, 21] {
        println!("I like the number {}", i);
    }
}

// output:
// I like the number 52
// I like the number 49
// I like the number 21

或者一个实际的迭代器:

fn main() {
    // note: `&str` also has a `.bytes()` iterator.
    // Rust's `char` type is a "Unicode scalar value"
    for c in "rust".chars() {
        println!("Give me a {}", c);
    }
}

// output:
// Give me a r
// Give me a u
// Give me a s
// Give me a t

即使迭代器项被过滤、映射和展平:

fn main() {
    for c in "SuRPRISE INbOUND"
        .chars()
        .filter(|c| c.is_lowercase())
        .flat_map(|c| c.to_uppercase())
    {
        print!("{}", c);
    }
    println!();
}

// output: UB

您可以从函数返回闭包:

fn make_tester(answer: String) -> impl Fn(&str) -> bool {
    move |challenge| {
        challenge == answer
    }
}

fn main() {
    // you can use `.into()` to perform conversions
    // between various types, here `&'static str` and `String`
    let test = make_tester("hunter2".into());
    println!("{}", test("******"));
    println!("{}", test("hunter2"));
}

您甚至可以将对函数的某些参数的引用移动到它返回的闭包中:

fn make_tester<'a>(answer: &'a str) -> impl Fn(&str) -> bool + 'a {
    move |challenge| {
        challenge == answer
    }
}

fn main() {
    let test = make_tester("hunter2");
    println!("{}", test("*******"));
    println!("{}", test("hunter2"));
}

// output:
// false
// true

或者,使用省略的生命周期:

fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
    move |challenge| {
        challenge == answer
    }
}

有了这个,我们达到了 30 分钟的预计阅读时间,你应该能够阅读你在网上找到的大部分 Rust 代码。

编写 Rust 与阅读 Rust 的体验截然不同。 一方面,你不是在阅读问题的解决方案,你实际上是在解决它。 另一方面,Rust 编译器提供了很多帮助。

对于上面犯的所有故意错误(“此代码是非法的”等),rustc 总是有很好的错误消息和有见地的建议。

当缺少提示时,编译器团队不怕添加它。

如需更多 Rust 材料,您可能需要查看:

RUST 编程书籍 RUST 编码示例 Read RUST This Week In Rust Blog About RUST

全部评论(0)