fn

main

()

{

println

“hello rust”

);

}

隨著 Facebook 的 Libra 專案出爐,Rust 進入了大家眼中,這是 Rust 有史以來最大的專案,它的旅程可能才剛剛開始。

雖然你可能還不太瞭解 Rust,但在開發者眼中,Rust 真香!

連續 4 年,在 Stack Overflow 開發者「最受喜愛程式語言」評選中,Rust 都是第一名。

#FormatImgID_1##FormatImgID_2#

2015 年 5 月 15 日,Rust 正式釋出了 1。0 版本。

4 年來,它憑藉著「安全」和「高併發」兩個特性,受到了越來越多開發者的喜愛。

Rust 正以勢如破竹之勢佔領區塊鏈新興專案市場,很多著名的老專案也在考慮轉向使用 Rust 重寫。

Rust 的語言特性(安全、高效能、併發程式設計)與區塊鏈的特性(分散式、加密、安全敏感)天生契合,很多著名的區塊鏈專案已經選擇使用Rust 作為其開發語言,包括:Parity、Polkadot、Substrate、Grin、Ethereum 經典、Holochain、Cardano-Rust、Exonum、Lighthouse、Nimiq、Nervos、Conflux-Rust、Codechain、Witnet 等,更不用說即將到來的 Libra。

相信,選擇使用 Rust 作為第一開發語言的區塊鏈專案也會越來越多,我們會迎來一波的 Rust 語言學習高潮,而區塊鏈開發者的薪資有多高,相信大家都清楚。

實驗樓上線了一門免費的 Rust 教程 —— 《透過例子學 Rust》。

課程改編自經典教材《Rust By Example》,並根據教材內容配置了線上實驗環境,和挑戰測試。每一個知識點都有配套的例項和小練習,讓大家輕鬆地掌握這門語言。

《Rust By Example》的Github倉庫地址:rust-lang-cn/rust-by-example-cn

以下是課程第一節內容:

課程介紹

Rust 是一門注重安全(safety)、速度(speed)和併發(concurrency)的現代系統程式語言。Rust 透過記憶體安全來實現以上目標,但不用垃圾回收機制(garbage collection, GC)。本課程為《透過例子學 Rust》的線上實驗版本,透過線上實驗一系列程式例子,一步步完成 Rust 程式語言的入門。歡迎在課程倉庫中參與修訂和完善,倉庫地址見 透過例子學 Rust - 線上實驗版。所有文件內容版權跟隨中文及英文原文件的版權(版權為 MIT 協議 或 Apache 協議)。

知識點

本節實驗的主要內容包括以下知識點:

課程介紹

如何編寫第一個程式

Hello World 程式詳解

註釋

格式化輸出

實驗列表

課程實驗根據原版教程內容進行規劃,每個實驗對應原版教程中的一個或多個章節:

Hello World - 從經典的 “Hello World” 程式開始學習。

原生型別 - 學習有符號整型,無符號整型和其他原生型別。

自定義型別 - 結構體

struct

和 列舉

enum

變數繫結 - 變數繫結,作用域,變數遮蔽。

型別系統 - 學習改變和定義型別。

型別轉換

表示式

流程控制 -

if

/

else

for

,以及其他流程控制有關內容。

函式 - 學習方法、閉包和高階函式。

模組 - 使用模組來組織程式碼。

crate

- crate 是 Rust 中的編譯單元。學習建立一個庫。

cargo - 學習官方的 Rust 包管理工具的一些基本功能。

屬性 - 屬性是應用於某些模組、crate 或項的元資料(metadata)。

泛型 - 學習編寫能夠適用於多型別引數的函式或資料型別。

作用域規則 - 作用域在所有權(ownership)、借用(borrowing)和生命週期(lifetime)中起著重要作用。

特性 trait - trait 是對未知型別 (

Self

) 定義的方法集。

錯誤處理 - 學習 Rust 語言處理失敗的方式。

標準庫型別 - 學習

std

標準庫提供的一些自定義型別。

標準庫更多介紹 - 更多關於檔案處理、執行緒的自定義型別。

測試 - Rust 語言的各種測試手段。

不安全操作,相容性和補充內容

現在我們開始進入到第一個 Hello World 實驗吧!

Hello World

我們的第一個程式將列印傳說中的 “Hello World” 訊息,下面是完整的程式程式碼和編譯執行過程。這是傳統的 Hello World 程式的原始碼。首先,在實驗樓 WebIDE 中

/home/project

目錄下新建

hello。rs

檔案,編寫以下程式碼(以

//

開頭的註釋內容可以不必輸入):

// 這是註釋內容,將會被編譯器忽略掉

// 可以單擊那邊的按鈕 “Run” 來測試這段程式碼 ->

// 若想用鍵盤操作,可以使用快捷鍵 “Ctrl + Enter” 來執行

// 這段程式碼支援編輯,你可以自由地修改程式碼!

// 透過單擊 “Reset” 按鈕可以使程式碼恢復到初始狀態 ->

// 這是主函式

fn

main

()

{

// 呼叫編譯生成的可執行檔案時,這裡的語句將被執行。

// 將文字列印到控制檯

println

“Hello World!”

);

}

連續 4 年成為“開發者最喜歡的語言”,Rust 憑啥這麼香?

println!

是一個 宏(macros),可以將文字輸出到控制檯(console)。在實驗樓 WebIDE 終端中執行以下命令,使用 Rust 的編譯器

rustc

從源程式生成可執行檔案:

$

cd

/

home

/

project

$

rustc

hello

rs

使用

rustc

編譯後將得到可執行檔案

hello

,使用以下命令來執行生成的檔案

hello

$

/

hello

執行後的結果如下所示:

連續 4 年成為“開發者最喜歡的語言”,Rust 憑啥這麼香?

動手試一試

請嘗試下在你的

hello。rs

程式中增加一行程式碼,再一次使用宏

println!

,得到下面結果:

Hello

World

I

‘m

a

Rustacean

註釋

註釋對任何程式都不可缺少,同樣 Rust 支援幾種不同的註釋方式。

普通註釋,其內容將被編譯器忽略掉:

// 單行註釋,註釋內容直到行尾。

/* 塊註釋, 註釋內容一直到結束分隔符。*/

文件註釋,其內容將被解析成 HTML 幫助文件:

/// 為接下來的項生成幫助文件。

//! 為註釋所屬於的項(譯註:如 crate、模組或函式)生成幫助文件。

fn

main

()

{

// 這是行註釋的例子

// 注意有兩個斜線在本行的開頭

// 在這裡面的所有內容都不會被編譯器讀取

// println!(“Hello, world!”);

// 請執行一下,你看到結果了嗎?現在請將上述語句的兩條斜線刪掉,並重新執行。

/*

* 這是另外一種註釋——塊註釋。一般而言,行註釋是推薦的註釋格式,

* 不過塊註釋在臨時註釋大塊程式碼特別有用。/* 塊註釋可以 /* 巢狀, */ */

* 所以只需很少按鍵就可註釋掉這些 main() 函式中的行。/*/*/* 自己試試!*/*/*/

*/

/*

注意,上面的例子中縱向都有 `*`,這只是一種風格,實際上這並不是必須的。

*/

// 觀察塊註釋是如何簡單地對錶達式進行修改的,行註釋則不能這樣。

// 刪除註釋分隔符將會改變結果。

let

x

=

5

+

/* 90 + */

5

println

“Is `x` 10 or 100? x = {}”

x

);

}

格式化輸出

列印操作由

std::fmt

裡面所定義的一系列

來處理,包括:

format!

:將格式化文字寫到

字串

(String)。

譯註:

字串

是返回值不是引數。

print!

:與

format!

類似,但將文字輸出到控制檯(

io::stdout

)。

println!

: 與

print!

類似,但輸出結果追加一個換行符。

eprint!

:與

format!

類似,但將文字輸出到標準錯誤(

io::stderr

)。

eprintln!

:與

eprint!

類似,但輸出結果追加一個換行符。

這些宏都以相同的做法解析(parse)文字。另外有個優點是格式化的正確性會在編譯時檢查。新建

format。rs

檔案,編寫程式碼如下。

fn

main

()

{

// 通常情況下,`{}` 會被任意變數內容所替換。

// 變數內容會轉化成字串。

println

“{} days”

31

);

// 不加字尾的話,31 就自動成為 i32 型別。

// 你可以新增字尾來改變 31 的型別。

// 用變數替換字串有多種寫法。

// 比如可以使用位置引數。

println

“{0}, this is {1}。 {1}, this is {0}”

“Alice”

“Bob”

);

// 可以使用命名引數。

println

“{subject} {verb} {object}”

object

=

“the lazy dog”

subject

=

“the quick brown fox”

verb

=

“jumps over”

);

// 可以在 `:` 後面指定特殊的格式。

println

“{} of {:b} people know binary, the other half don’t”

1

2

);

// 你可以按指定寬度來右對齊文字。

// 下面語句輸出 “ 1”,5 個空格後面連著 1。

println

“{number:>width$}”

number

=

1

width

=

6

);

// 你可以在數字左邊補 0。下面語句輸出 “000001”。

println

“{number:>0width$}”

number

=

1

width

=

6

);

// println! 會檢查使用到的引數數量是否正確。

println

“My name is {0}, {1} {0}”

“Bond”

);

// 改正 ^ 補上漏掉的引數:“James”

// 建立一個包含單個 `i32` 的結構體(structure)。命名為 `Structure`。

#[allow(dead_code)]

struct

Structure

i32

);

// 但是像結構體這樣的自定義型別需要更復雜的方式來處理。

// 下面語句無法執行。

println

“This struct `{}` won‘t print。。。”

Structure

3

));

// 改正 ^ 註釋掉此行。

}

std::fmt

包含多種

traits

(trait 有「特徵,特性」等意思)來控制文字顯示,其中重要的兩種 trait 的基本形式如下:

fmt::Debug

:使用

{:?}

標記。格式化文字以供除錯使用。

fmt::Display

:使用

{}

標記。以更優雅和友好的風格來格式化文字。

上例使用了

fmt::Display

,因為標準庫提供了那些型別的實現。若要列印自定義型別的文字,需要更多的步驟。

動手試一試

改正上面程式碼中的兩個錯誤(見程式碼註釋中的「改正」),使它可以沒有錯誤地執行。

再用一個

println!

宏,透過控制顯示的小數位數來列印:

Pi is roughly 3。142

(Pi 約等於 3。142)。為了達到練習目的,使用

let pi = 3。141592

作為 Pi 的近似 值。

提示:設定小數位的顯示格式可以參考文件 std::fmt。

除錯(Debug)

所有的型別,若想用

std::fmt

的格式化

trait

打印出來,都要求實現這個

trait

。自動的實現只為一些型別提供,比如

std

庫中的型別。所有其他型別都必須手動實現。

fmt::Debug

這個

trait

使這項工作變得相當簡單。所有型別都能推導(

derive

,即自 動建立)

fmt::Debug

的實現。但是

fmt::Display

需要手動實現。

// 這個結構體不能使用 `fmt::Display` 或 `fmt::Debug` 來進行列印。

struct

UnPrintable

i32

);

// `derive` 屬性會自動建立所需的實現,使這個 `struct` 能使用 `fmt::Debug` 列印。

#[derive(Debug)]

struct

DebugPrintable

i32

);

所有

std

庫型別都天生可以使用

{:?}

來列印。新建

format1。rs

檔案,編寫程式碼如下:

// 推導 `Structure` 的 `fmt::Debug` 實現。

// `Structure` 是一個包含單個 `i32` 的結構體。

#[derive(Debug)]

struct

Structure

i32

);

// 將 `Structure` 放到結構體 `Deep` 中。然後使 `Deep` 也能夠列印。

#[derive(Debug)]

struct

Deep

Structure

);

fn

main

()

{

// 使用 `{:?}` 列印和使用 `{}` 類似。

println

“{:?} months in a year。”

12

);

println

“{1:?} {0:?} is the {actor:?} name。”

“Slater”

“Christian”

actor

=

“actor’s”

);

// `Structure` 也可以列印!

println

“Now {:?} will print!”

Structure

3

));

// 使用 `derive` 的一個問題是不能控制輸出的形式。

// 假如我只想展示一個 `7` 怎麼辦?

println

“Now {:?} will print!”

Deep

Structure

7

)));

}

在實驗樓 WebIDE 終端中編譯並執行程式。

$

rustc

format1

rs

$

/

format1

執行結果如下所示:

連續 4 年成為“開發者最喜歡的語言”,Rust 憑啥這麼香?

所以

fmt::Debug

確實使這些內容可以列印,但是犧牲了一些美感。Rust 也透過

{:#?}

提供了「美化列印」的功能:

#[derive(Debug)]

struct

Person

<

‘a

>

{

name

&

’a

str

age

u8

}

fn

main

()

{

let

name

=

“Peter”

let

age

=

27

let

peter

=

Person

{

name

age

};

// 美化列印

println

“{:#?}”

peter

);

}

將上面程式碼新增到

format1。rs

中後,編譯並執行的結果如下所示:

連續 4 年成為“開發者最喜歡的語言”,Rust 憑啥這麼香?

你可以透過手動實現

fmt::Display

來控制顯示效果。

顯示(Display)

fmt::Debug

通常看起來不太簡潔,因此自定義輸出的外觀經常是更可取的。這需要透過手動實現

fmt::Display

來做到。

fmt::Display

採用

{}

標記。實現方式看起來像這樣:

// (使用 `use`)匯入 `fmt` 模組使 `fmt::Display` 可用

use

std

::

fmt

// 定義一個結構體,咱們會為它實現 `fmt::Display`。以下是個簡單的元組結構體

// `Structure`,包含一個 `i32` 元素。

struct

Structure

i32

);

// 為了使用 `{}` 標記,必須手動為型別實現 `fmt::Display` trait。

impl

fmt

::

Display

for

Structure

{

// 這個 trait 要求 `fmt` 使用與下面的函式完全一致的函式簽名

fn

fmt

&

self

f

&

mut

fmt

::

Formatter

->

fmt

::

Result

{

// 僅將 self 的第一個元素寫入到給定的輸出流 `f`。返回 `fmt:Result`,此

// 結果表明操作成功或失敗。注意 `write!` 的用法和 `println!` 很相似。

write

f

“{}”

self

0

}

}

fmt::Display

的效果可能比

fmt::Debug

簡潔,但對於

std

庫來說,這就有一個問題。模稜兩可的型別該如何顯示呢?舉個例子,假設標準庫對所有的

Vec

都實現了同一種輸出樣式,那麼它應該是哪種樣式?下面兩種中的一種嗎?

Vec

/:/etc:/home/username:/bin

(使用

分割)

Vec

1,2,3

(使用

分割)

我們沒有這樣做,因為沒有一種合適的樣式適用於所有型別,標準庫也並不擅自規定一種樣式。對於

Vec

或其他任意

泛型容器

(generic container),

fmt::Display

都沒有實現。因此在這些泛型的情況下要用

fmt::Debug

。這並不是一個問題,因為對於任何非泛型的容器型別,

fmt::Display

都能夠實現。新建

display。rs

檔案,編寫程式碼如下:

use

std

::

fmt

// (使用 `use`)匯入 `fmt` 模組使 `fmt::Display` 可用

// 帶有兩個數字的結構體。推匯出 `Debug`,以便與 `Display` 的輸出進行比較。

#[derive(Debug)]

struct

MinMax

i64

i64

);

// 實現 `MinMax` 的 `Display`。

impl

fmt

::

Display

for

MinMax

{

fn

fmt

&

self

f

&

mut

fmt

::

Formatter

->

fmt

::

Result

{

// 使用 `self。number` 來表示各個資料。

write

f

“({}, {})”

self

0

self

1

}

}

// 為了比較,定義一個含有具名欄位的結構體。

#[derive(Debug)]

struct

Point2D

{

x

f64

y

f64

}

// 類似地對 `Point2D` 實現 `Display`

impl

fmt

::

Display

for

Point2D

{

fn

fmt

&

self

f

&

mut

fmt

::

Formatter

->

fmt

::

Result

{

// 自定義格式,使得僅顯示 `x` 和 `y` 的值。

write

f

“x: {}, y: {}”

self

x

self

y

}

}

fn

main

()

{

let

minmax

=

MinMax

0

14

);

println

“Compare structures:”

);

println

“Display: {}”

minmax

);

println

“Debug: {:?}”

minmax

);

let

big_range

=

MinMax

-

300

300

);

let

small_range

=

MinMax

-

3

3

);

println

“The big range is {big} and the small is {small}”

small

=

small_range

big

=

big_range

);

let

point

=

Point2D

{

x

3。3

y

7。2

};

println

“Compare points:”

);

println

“Display: {}”

point

);

println

“Debug: {:?}”

point

);

// 報錯。`Debug` 和 `Display` 都被實現了,但 `{:b}` 需要 `fmt::Binary`

// 得到實現。這語句不能執行。

// println!(“What does Point2D look like in binary: {:b}?”, point);

}

程式執行的結果如下所示:

連續 4 年成為“開發者最喜歡的語言”,Rust 憑啥這麼香?

篇幅有限,課程共有 22 個章節,全部免費,歡迎在實驗樓邊敲程式碼邊學習: