半小時學Rust

半小時學Rust

文章翻譯自英文博客,內容有刪減。在此感謝原作者分享的精神!
原文地址:A half-hour to learn Rust
原文作者:amos loves to tinker

說明:文章將根據個人理解深度,將不定期修改編輯,也歡迎同學提出寶貴建議!

有時爲了加強編程語言的熟練程度,我們需要閱讀很多相關文檔。但如果不知道什麼意思你如何去閱讀呢?
在本文中,我將介紹儘可能多的Rust Snippets,而不去過多關注某幾個概念,而且會解釋他們所包含關鍵字和符號的意義。

準備好了嗎?那開始吧!

變量綁定

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();

以下劃線開始的變量名稱是regular名稱,編譯器在該名稱未使用時不會給出警告信息:

// 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;

也就是說一個語句可以衍生(span)多行:

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

(待會解釋該代碼段什麼意思!)

函數

fn 聲明一個函數。
(1) 無返回值的函數:

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

(2) 返回一個i32整數值的函數,箭頭->表示其返回值類型:

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
    }
}

match表達式:

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是一個crate (庫),cmp是一個模塊(代碼文件),min是一個函數:

let least = std::cmp::min(34, 90); // this is 34

use指令可用於將其他名稱空間中的名稱“引入範圍”:

use std::cmp::min;

let least = min(7, 1); // this is 1

在use指令中,花括號的另一含義是:“globs”。假如我們要同時引用minmax,我們可以做如下操作:

// 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};

通配符*允許你從一個命名空間中引入每個symbol:

// 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,Option和Result)

關鍵字struct用於結構體聲明:

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

可以使用結構文字初始化它們:

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

match必須是詳盡的:至少有一個能匹配的分支。

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
    }
}

若列出所有匹配分支是比較困難的,那麼下劃線_可以用作“catch-all”模式:

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
}

Traits

Trait 是多種類型共同擁有的一種性質:

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

你可以實現爲:
(1)如果要實現外部定義的 trait 需要先將其導入作用域。
(2)不允許對外部類型實現外部 trait;
(3)可以對外部類型實現自定義的 trait;
(4)可以對自定義類型上實現外部 trait。
這些規則稱爲“孤兒規則”。
(1) 爲自定義類型實現Trait的例子:

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"
}

(2) 爲外部類型(原始類型,比如i32等)實現自定義Trait:

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"
}

(3)爲自定義類型實現外部Trait:

// 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,
        }        
    }
}

一些Trait是標籤-它們不是說某一類型實現某些方法,而是說某些事情可以藉助於某一類型來完成。比如說,i32實現了CopyTrait.(簡言之,i32Copy),因此以下例子是可以正常工作的。

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 Trait,所以這樣做會有問題:

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方法也可以引用或者可變引用作爲自身:

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);

標籤Trait像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.
}

有些Trait可以通過derive屬性自動實現標籤Trait:

#[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`.
}

類型參數通常具有約束,因此您實際上可以對它們做一些事情。最簡單的限制就是Trait名稱:

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 = {}");
}

約束可能更復雜:它們可能需要一個類型參數來實現多個Traits:

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

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

泛型函數可以被認爲是名稱空間,其中包含無限個具有不同具體類型的函數。
和使用crate, module,type和泛型函數一樣,可以使用::來“導航”;

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

這被稱爲渦輪魚語法,因爲::<>看起來像一條魚。結構體也可以是泛型的:

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

一些方法也是Panic,比如Option類型能爲Some(x),也可能爲None。如果Option調用.unwrap(),如果爲None,那麼將會Panic。

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

    let o2: Option<i32> = None;
    o2.unwrap(); // this is panics!
}
// output: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21

Option不是一個結構體,而是一個枚舉類型,且只有兩個變量;

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

impl<T> Option<T> {
    fn unwrap(self) -> T {
        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 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21

Result也是一個枚舉類型:

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

它也會在Result值是Err(E)時發生panic。

Lifetime

變量綁定有一個生命週期“lieftime”:

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.   
}

具有引用參數的函數可以使用具有不同生命週期的借用來調用:
(1)All functions that take references are generic
(2)生命週期是泛型參數;
生命週期的名稱起始字符帶有單引號'

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

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

返回引用的生命週期要依賴於某一個函數參數的生命週期:

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

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

當函數只有一個輸入生命週期(帶有生命週期限制的函數參數),沒有必要去標註生命週期,所有項都具有相同的生命週期,因此以下兩個函數是等價的:

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(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: &'a i32) -> NumRef<'_> {
    NumRef {x: &x}
}

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

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 cannot 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,其在程序的整個生命週期內都有效,以下爲String語法的例子:

struct Person {
    name: &'static str,
}

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

但是已有所主的字符串是不能static的,以下例子中的引用生命週期是不能長於所引用變量的生命週期的:

struct Person {
    name: &'static str,
}

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

In that last example, the local name is not a &static str, it’s a String. It’s been allocated dynamically, and it will be freed. Its lifetime is less than the whole program (even though it happens to be in main)."

爲了在Person結構體中存儲一個非‘static字符串,需要:
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中很多類型有ownednon-owned變量:
(1)Strings:String是owned, &str是引用的;
(2)Paths: PathBuf是owned, &Path是引用的;
(3)Collections: Vec<T>是owned, &[T]是引用的;
Rust有slice(切片),他們是多連續元素的引用。可以通過以下例子中的方式借用動態數組(vector):

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

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

以上列子並非難以理解,查詢操作符(foo[index])重載了IndexIndexMutTrait。..語義表示範圍,僅僅是一些在標準庫中定義的結構體。且索引範圍是半開半閉區間內的元素,如果最右端的前面加上=

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
}

借用規則同樣可應用於slices:

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語義時,你可以簡單地這樣做。
首先看下沒有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
}

但當有Copy語義時:

// 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
}

閉包

閉包是具有某些捕獲上下文的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.
}

一個FnMut需要可變借用才能被調用,因此它在某一時刻只能被調用一次,比如以下例子是合法的:

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);
    }
}

或者是切片(slice)

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 "sHE'S brOKen"
        .chars()
        .filter(|c| c.is_uppercase() || !c.is_ascii_alphabetic())
        .flat_map(|c| c.to_lowercase())
    {
        print!("{}", c);
    }
    println!();
}

// output: he's ok

依舊可以從一個函數返回一個閉包:

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始終具有非常好的錯誤消息和有見地的建議。而且,當缺少提示時,編譯器團隊不會害怕添加它。
也許你需要查看更多的資料:
The Rust Book
Rust By Example
Read Rust
This Week In Rust

我也寫一些Rust相關的博客和發一些Rust相關的twitter,因此如果你比較喜歡這篇文章,你知道該怎麼做了!

Have fun!

發佈了35 篇原創文章 · 獲贊 9 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章