【翻譯】半小時快速瞭解Rust(一)
一個人為了更好地掌握一門程式語言,不得不去進行大量的閱讀。但是如果你都不知道程式碼的意思,又怎麼能進行大量閱讀呢?
在本文中,我將盡我所能去剖析儘可能多的Rust程式碼片段並解釋其中包含的符號和關鍵字的含義。
準備好了嗎?Go!
let
關鍵字表示一個變數繫結(binding)
let x; // 宣告 “x”
x = 42; // 把42賦值給“x”
上面的程式碼也可以寫成一行:
let x = 42;
你可以用
:
顯示指定變數型別,即型別標註(type annotation):
let x: i32; // `i32` 是一個有符號的32位(32-bit)整數
x = 42;
// 還有 i8, i16, i32, i64, i128
// 以及無符號的 u8, u16, u32, u64, u128
上面的程式碼也可以寫成一行:
let x: i32 = 42;
如果你聲明瞭一個變數但是要在後面對它進行初始化,編譯器就會阻止你在這個變數初始化之前使用它。
let x;
foobar(x); // 錯誤: 借用一個可能沒有初始化的變數: `x`
x = 42;
但是,下面這樣寫就沒有問題了:
let x;
x = 42;
foobar(x); // `x` 的型別會從這裡推匯出來
下劃線
_
是一種特殊的變數,更準確的說叫“預設變數”。這表示丟棄某些東西:
// 因為42是常量,這裡什麼也不做
let _ = 42;
// 這裡呼叫`get_thing`但是丟棄了它的結果
let _ = get_thing();
以下劃線開頭的變數名也是正常的變數名,只是告訴編譯器在這麼變數未被使用的情況下也不要警告:
//我們可能最終會用到`_x`,但是我們的工作還沒完成,這個時候我們只是想要避免編譯期的警告
let _x = 42;
相同的變數名也可以進行不同的繫結-你可以遮蓋(shadow)一個變數繫結:
let x = 13;
let x = x + 3;
// 在第二行之後使用的`x`都指向第二個`x`,
// 第一個 `x` 就不存在了。
Rust裡有元組(tuple),元組你可以認為是一種具有固定的長度的集合,集合裡面的值可以是不同型別的。
let pair = (‘a’, 17);
pair。0; // 這個值 ‘a’
pair。1; // 這個值 17
如果我們真的想標註***pair***的型別,我們可以這樣寫:
let pair: (char, i32) = (‘a’, 17);
元組(tuple)在賦值的時候可以被拆分,意思是指元組可以被拆分成獨立的欄位:
let (some_char, some_int) = (‘a’, 17);
// 現在, `some_char` 的值是 ‘a’, `some_int` 的值是 17
當函式函式返回一個元組的時候,這是非常有用的:
let (left, right) = slice。split_at(middle);
當然,在拆分元組的時候,
_
可以用於丟棄其中的一部分:
let (_, right) = slice。split_at(middle);
分號表示宣告(statement)語句的結束:
let x = 3;
let y = 5;
let z = y + x;
這也意味著宣告(statement)語句可以跨越很多行:
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
}
一對大括號聲明瞭一個程式碼塊,程式碼塊有自己的作用域(own scope)
// 下面的程式碼會先輸出 “in”, 然後輸出 “out”
fn main() {
let x = “out”;
{
// 這是另一個不同的 `x`
let x = “in”;
println!(x);
}
println!(x);
}
塊(block)也是表示式,這意味著塊(block)
可以作為值:
// 這個:
let x = 42;
// 等價於下面:
let x = { 42 };
在塊裡面,可以有多個
宣告(statements)
:
let x = {
let y = 1; // 第一個宣告
let z = 2; // 第二個宣告
y + z // 這是結尾 - 整個塊返回的最終結果
};
這就是為什麼說“函式結尾處省略分號”和返回結果(returning)是一樣的。比如下面的程式碼就是等價的:
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(3, 8); //least的值是 3
use
指令可以用於把其他命名空間裡的變數帶過來(bring in scope):
use std::cmp::min;
let least = min(7, 1); // this is 1
在
use
指令中,大括號有另一個意思:它們是“一團”(globs)。如果我們想要一起倒入
min
和
max
,我們可以這樣寫:
// 這樣寫可以:
use std::cmp::min;
use std::cmp::max;
// 這樣寫也可以:
use std::cmp::{min, max};
// 這樣寫也可以:
use std::{cmp::min, cmp::max};
使用一個萬用字元(
*
)可以匯入一個名稱空間內的所有符號(symbol):
// 不僅匯入了 `min` and `max` ,還匯入了其他很多東西。
use std::cmp::*;
str
是一種原始型別,但是很多非原始型別預設也被引入了:
// `Vec` 是一個普通的結構體, 不是一個原始型別
let v = Vec::new();
// 這是和上面相同的程式碼,但是帶上了Vec的完整路徑
let v = std::vec::Vec::new();
上面的程式碼可以工作是因為Rust在每個模組(module)的開頭插入了下面的程式碼:
use std::prelude::v1::*;
(上面這些程式碼重新匯入了很多符號,像
Vec
,
String
,
Option
和
Result
)。 宣告結構體使用
struct
關鍵字:
struct Vec2 {
x: f64, // 64位浮點數, 也叫 “雙精度”
y: f64,
}
它們可以用結構體字面量(struct literals)來初始化:
let v1=Vec2{x:1。0, y:3。0};
let v2=Vec2{y:2。0, x:4。0};
//順序不重要,只要名字可以對應上就可以
從另一個結構初始化剩餘的欄位可以使用縮寫的形式:
let v3 = Vec2 {
x: 14。0,
。。v2
};
這叫做“結構體更新語法(struct update syntax)”,而且只能寫在結構體最後的位置並且後面不能有逗號。
注意,剩餘的欄位可以指所有的欄位:
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` 現在是 3。0, `y` 現在是 `6。0`
以及下面這個:
let Vec2 { x, 。。 } = v;
// 這裡丟棄了 v。y
let
模式可以在
if
語句中用作條件判斷(conditions):
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);
}
}
// 上面的程式碼輸出如下:
// Odd number: 1
// Even number: 2
match
匹配也是模式(patterns),就想
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),
}
}
// 這段程式碼的輸出和上一次的一樣
一個
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
}
}
如果很難列出所有的情況,可以使用
_
作為“捕獲全部”的模式:
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());
// 這段程式碼會列印 “positive? false”
}
變數繫結預設是不可變的(immutable):
fn main() {
let n = Number {
odd: true,
value: 17,
};
n。odd = false; // 錯誤: 不能對`n。odd`賦值因為`n`沒有宣告為可變的
}
一個不可變的變數繫結不能修改其內部的資訊(就行我們剛剛嘗試的),並且它自身也不能被賦值:
fn main() {
let n = Number {
odd: true,
value: 17,
};
n = Number {
odd: false,
value: 22,
}; // 錯誤: 不能對不可變變數`n`賦值兩次
}
mut
讓一個變數繫結可以被修改(mutable):
fn main() {
let mut n = Number {
odd: true,
value: 17,
}
n。value = 19; // all good
}
Trait是可以被多個型別共同擁有(譯者注:這裡trait不進行翻譯,因為看到一些書籍裡保留了原詞):
trait Signed {
fn is_strictly_negative(self) -> bool;
}
你可以實現:
在任何人的型別上實現你的trait
在你定義的型別上實現任何人的trait
但是不能給外部的型別實現外部的trait
以上被稱為“孤兒原則”。
下面是在我們定義的型別上實現我們定義的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”
}
在外部型別上實現我們定義的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”
}
在我們的型別上實現一個外部的trait:
// the `Neg` trait用於過載減號`-`, 也就是一元減法運算子
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; // 因為我們實現了`Neg` trait,這裡才是行得通的。
println!(“{}”, m。value); // 列印輸出: “-987”
}
一個
impl
塊總是針對一個型別,所以,在這個塊裡,
Self
表示那個型別:
impl std::ops::Neg for Number {
type Output = Self;
fn neg(self) -> Self {
Self {
value: -self。value,
odd: self。odd,
}
}
}
一些traits是起標識作用的-它們不說一個型別實現了某些方法,他們說一些事情只能被某個型別完成。
例如,
i32
實現了
Copy
trait(簡而言之,就是
i32
是
Copy
),因此,下面的程式碼是有效的:
fn main() {
let a: i32 = 15;
let b = a; // `a` 被複製
let c = a; // `a` 再次被複製
}
下面的程式碼也是行得通的:
fn print_i32(x: i32) {
println!(“x = {}”, x);
}
fn main() {
let a: i32 = 15;
print_i32(a); // `a` 被複製
print_i32(a); // `a` 再次被複製
}
但是
Number
結構體不是
Copy
, 所以下面的程式碼行不通:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `n` 被移動到 `m`
let o = n; // 錯誤: 使用以及被移動過的值: `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
}
這樣子也是行得通的:如果一個函式使用一個可變引用(mutable 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() {
// 這次, `n` 是可變的
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n); // `n`被可變借用 - 一切都是顯式的
print_number(&n);
}
Trait方法也可以使用
self
的引用(預設不可變)或者可變引用:
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { 。。*self }
}
}
當執行trait方法時,接收者(receiver)是隱式借用:
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);
標識traits像
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。
}
一些traits會經常被用到,它們可以透過使用
derive
屬性自動被實現:
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// this expands to `impl Clone for Number` and `impl Copy for Number` blocks。
函式可以是泛型的(generic):
fn foobar
// do something with `arg`
}
它們可以有多個型別引數而不是具體的型別用於函式的宣告和函式體:
fn foobar
// do something with `left` and `right`
}
型別引數通常會有約束,所以你可以用這些約束來做一些事情。
最簡單的約束就是trait變數名:
fn print
println!(“value = {}”, value);
}
fn print
println!(“value = {:?}”, value);
}
型別引數約束還有一種更長一點兒的語法:
fn print
where
T: Display,
{
println!(“value = {}”, value);
}
約束可以更復雜:它們可以要求一個型別引數實現多個trait:
use std::fmt::Debug;
fn compare
where
T: Debug + PartialEq,
{
println!(“{:?} {} {:?}”, left, if left == right { “==” } else { “!=” }, right);
}
fn main() {
compare(“tea”, “coffee”);
// prints: “tea” != “coffee”
}
泛型函式可以被認為是名稱空間,包含了無數的具體型別的函式。
類似於crates和modules以及types,泛型函式也可以使用
::
來探索(explored、navigated?)(譯者注:這裡不知道該怎麼翻譯,應該是說可以使用
::
符號來引用裡面針對具體型別的函式):
fn main() {
use std::any::type_name;
println!(“{}”, type_name::
println!(“{}”, type_name::<(f64, char)>()); // prints “(f64, char)”
}
這種方式被親切地稱為turbofish語法(turbofish syntax),因為::<>看起來像一條魚。
結構體也可以是泛型的:
struct Pair
a: T,
b: T,
}
fn print_type_name
println!(“{}”, std::any::type_name::
}
fn main() {
let p1 = Pair { a: 3, b: 9 };
let p2 = Pair { a: true, b: false };
print_type_name(&p1); // prints “Pair
print_type_name(&p2); // prints “Pair
}
標準庫裡的
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
print_type_name(&v2); // prints “Vec
}
提及
Vec
,它有一個能多少給出一些“vec literals”的宏(譯者注:由於譯者水平有限,這裡的vec literals暫時不知道如何翻譯,敬請諒解):
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1); // prints “Vec
print_type_name(&v2); // prints “Vec
}
所有的形如
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