半小時入門Rust,這是一篇Rust代碼風(fēng)暴
據(jù)說很多開發(fā)者一天入門 Python,兩天上手 Go,但到了 Rust 就會發(fā)現(xiàn)畫風(fēng)隱約有些不對。它從語法到特性,似乎都要復(fù)雜一些。本文介紹的就是 Rust,作者表示,通過解析大量代碼,「半個小時」就能入門 Rust。
Rust 是一門系統(tǒng)編程語言,專注于安全,尤其是并發(fā)安全。它支持函數(shù)式和命令式以及泛型等編程范式的多范式語言,且 TensorFlow 等深度學(xué)習(xí)框架也把它作為一個優(yōu)秀的前端語言。
Rust 在語法上和 C、C++類似,都由花括弧限定代碼塊,并有相同的控制流關(guān)鍵字,但 Rust 設(shè)計者想要在保證性能的同時提供更好的內(nèi)存安全。Rust 自 2016 年就已經(jīng)開源了,在各種開發(fā)者調(diào)查中,它也總能獲得「最受歡迎的語言」這一稱贊,目前該開源項目已有 42.9K 的 Star 量。
機(jī)器之心的讀者大多數(shù)都非常熟悉 Python,而 Rust 就沒那么熟悉了。在 Amos 最近的一篇博文中,他表示如果閱讀他的作品,我們半個小時就能入門 Rust。因此在這篇文章中,我們將介紹該博文的主要內(nèi)容,它并不關(guān)注于 1 個或幾個關(guān)鍵概念,相反它希望通過代碼塊縱覽 Rust 的各種特性,包括各種關(guān)鍵詞與符號的意義。
在 HackNews 上,很多開發(fā)者表示這一份入門教程非常實(shí)用,Rust 的入門門檻本來就比較高,如果再介紹各種復(fù)雜的概念與特性,很容易出現(xiàn)「從入門到勸退」。因此這種從實(shí)例代碼出發(fā)的教程,非常有意義。
從變量說起
let 能綁定變量:
- let x; // declare "x"
- x = 42; // assign 42 to "x"
- let x = 42; // combined in one line
可以使用 :來制定變量的數(shù)據(jù)類型,以及數(shù)據(jù)類型注釋:
- 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; // combined in one line
如果你聲明一個變量并在初始化之前就調(diào)用它,編譯器會報錯:
- 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
下劃線表示特殊的命名,或者更確切地說是「缺失的命名」,它和 Python 的用法有點(diǎn)像:
- // this does *nothing* because 42 is a constant
- let _ = 42;
- // this calls `get_thing` but throws away its result
- let _ = get_thing();
以下劃線開頭的命名是常規(guī)命名,只是編譯器不會警告它們未被使用:
- // 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;
相同命名的單獨(dú)綁定是可行的,第一次綁定的變量會取消:
- 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 有元組類型,可以將其看作是「不同數(shù)據(jù)類型值的定長集合」。
- let pair = ('a', 17);
- pair.0; // this is 'a'
- pair.1; // this is 17
如果真的想配置 pair 的數(shù)據(jù)類型,可以這么寫:
- let pair: (char, i32) = ('a', 17);
元組在賦值時可以被拆解,這意味著它們被分解成各個字段:
- let (some_char, some_int) = ('a', 17);
- // now, `some_char` is 'a', and `some_int` is 17
當(dāng)一個函數(shù)返還一個元組時會非常有用:
- let (left, right) = slice.split_at(middle);
當(dāng)然,在解構(gòu)一個元組時,可以只分離它的一部分:
- let (_, right) = slice.split_at(middle);
分號表示語句的結(jié)尾:
- 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);
函數(shù)來了
fn 聲明一個函數(shù)。下面是一個空函數(shù):
- fn greet() {
- println!("Hi there!");
- }
這是一個返還 32 位帶符號整數(shù)值的函數(shù)。箭頭表示返還類型:
- 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
- };
這也是為什么「省略函數(shù)末尾的分號」等同于加上了 Retrun,這些都是等價的:
- fn fair_dice_roll() -> i32 {
- return 4;
- }
- fn fair_dice_roll() -> i32 {
- 4
- }
if 條件語句也是表達(dá)式:
- fn fair_dice_roll() -> i32 {
- if feeling_lucky {
- 6
- } else {
- 4
- }
- }
match 匹配器也是一個表達(dá)式:
- fn fair_dice_roll() -> i32 {
- match feeling_lucky {
- true => 6,
- false => 4,
- }
- }
Dots 通常用于訪問某個對象的字段:
- let a = (10, 20);
- a.0; // this is 10
- let amos = get_some_struct();
- amos.nickname; // this is "fasterthanlime"
或者調(diào)用對象的方法:
- let nick = "fasterthanlime";
- nick.len(); // this is 14
雙冒號與此類似,但可對命名空間進(jìn)行操作。在此舉例中,std 是一個 crate (~ a library),cmp 是一個 module(~ a source file),以及 min 是個函數(shù):
- 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」,因此可以同時導(dǎo)入 min 以及 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};
通配符(*)允許從命名空間導(dǎo)入符號:
- // this brings `min` and `max` in scope, and many other things
- use std::cmp::*;
Types 也是命名空間和方法,它可以作為常規(guī)函數(shù)調(diào)用:
- let x = "amos".len(); // this is 4
- let x = str::len("amos"); // this is also 4
str 是一個基元數(shù)據(jù)類型,但在默認(rèn)情況下,許多非基元數(shù)據(jù)類型也在作用域中。
- // `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::*;
再說說結(jié)構(gòu)體
使用 struct 關(guān)鍵字聲明結(jié)構(gòu)體:
- struct Vec2 {
- x: f64, // 64-bit floating point, aka "double precision"
- y: f64,
- }
可以使用結(jié)構(gòu)語句初始化:
- 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
有一個快捷方式可以從另一個結(jié)構(gòu)體初始化本結(jié)構(gòu)體的其余字段:
- let v3 = Vec2 {
- x: 14.0,
- ..v2
- };
這就是所謂的「結(jié)構(gòu)體更新語法」只能發(fā)生在最后一個位置,不能在其后面再跟一個逗號。
注意其余字段可以表示所有字段:
- let v4 = Vec2 { ..v3 };
結(jié)構(gòu)體與元組一樣,可以被解構(gòu)。例如一個有效的 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
- }
- }
如果非常難實(shí)現(xiàn),_ 那么可以作用一個“包羅萬象”的模式:
- fn print_number(n: Number) {
- match n.value {
- 1 => println!("One"),
- 2 => println!("Two"),
- _ => println!("{}", n.value),
- }
- }
Type 別名
我們可以使用 type 關(guān)鍵字聲明另一類型的別名,然后就可以像使用一個真正的類型一樣使用這種類型。例如定義 Name 這種數(shù)據(jù)類型為字符串,后面就可以直接使用 Name 這種類型了。
你可以在方法中聲明不同的數(shù)據(jù)類型:
- 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"
- }
默認(rèn)情況下,聲明變量后它就就是不可變的,如下 odd 不能被重新賦值:
- 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
- }
不可變的變量聲明,其內(nèi)部也是不可變的,它也不能重新分配值:
- fn main() {
- let n = Number {
- odd: true,
- value: 17,
- };
- n = Number {
- odd: false,
- value: 22,
- }; // error: cannot assign twice to immutable variable `n`
- }
mut 可以使變量聲明變?yōu)榭勺兊模?/p>
- fn main() {
- let mut n = Number {
- odd: true,
- value: 17,
- }
- n.value = 19; // all good
- }
Traits 描述的是多種數(shù)據(jù)類型的共同點(diǎn):
- trait Signed {
- fn is_strictly_negative(self) -> bool;
- }
我們可以在我們定義的 Type 類型中定義 Traits:
- 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"
- }
外部類型(foreign type)中定義的 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"
- }
impl 模塊通常會帶有一個 Type 類型,所以在模塊內(nèi),Self 就表示該類型:
- impl std::ops::Neg for Number {
- type Output = Self;
- fn neg(self) -> Self {
- Self {
- value: -self.value,
- odd: self.odd,
- }
- }
- }
有一些traits只是作為標(biāo)記,它們并不是說 Type 類型實(shí)現(xiàn)了某些方法,它只是表明某些東西能通過Type類型完成。例如,i32 實(shí)現(xiàn)了Copy,那么以下代碼就是可行的:
- fn main() {
- let a: i32 = 15;
- let b = a; // `a` is copied
- let c = a; // `a` is copied again
- }
下面的代碼也是能運(yùn)行的:
- 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 的結(jié)構(gòu)體并不能用于 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`
- }
同樣下面的代碼也不會 Work:
- 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有一個不可變reference,那么 Copy 就是可行的:
- 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
- }
如果函數(shù)采用了可變reference,那也是可行的,只不過需要在變量聲明中帶上 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);
- }
Copy 這類標(biāo)記型的traits并不帶有方法:
- // 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 {}
現(xiàn)在 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.
- }
有一些traits很常見,它們可以通過使用derive 屬性自動實(shí)現(xiàn):
- #[derive(Clone, Copy)]
- struct Number {
- odd: bool,
- value: i32,
- }
- // this expands to `impl Clone for Number` and `impl Copy for Number` blocks.
看上去,整篇教程都在使用大量代碼解釋 Rust 的各種語句與用法??赡芪覀儠杏X博客結(jié)構(gòu)不是太明確,但是實(shí)例驅(qū)動的代碼學(xué)習(xí)確實(shí)更加高效。尤其是對于那些有一些編程基礎(chǔ)的同學(xué),他們可以快速抓住 Rust 語言的特點(diǎn)與邏輯。
最后,這篇文章并沒有展示博客所有的內(nèi)容,如果讀者想真正入門 Rust 語言,推薦可以查閱原博客。