連續 4 年成爲「開發者最喜歡的語言」,這門編程語言你瞭解過嗎?

fn main() {   println!("hello rust");}

隨着 Facebook 的 Libra 項目出爐,Rust 一會兒火了,這是 Rust 有史以來最大的項目,但隨着全球數字貨幣的興起,Rust 的旅程可能纔剛剛開始。編程

雖然你可能還不太瞭解 Rust,但在開發者眼中,Rust 真香!連續 4 年,在 Stack Overflow 開發者「最受喜好編程語言」評選中,Rust 都是第一名。小程序

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》哦。函數

接下來,你們就跟着我熟悉一下 Rust 的一些基礎語法吧,用 Rust 寫出你的第一個小程序。高併發

本文建議收藏,這樣隨時均可以拿出來鞏固一下基礎 Rust 知識。性能


如下是 《經過例子學 Rust》第一節內容:學習

簡介

Rust 是一門注重安全(safety)、速度(speed)和併發(concurrency)的現代系統編程語言。Rust 經過內存安全來實現以上目標,但不用垃圾回收機制(garbage collection, GC)。

本課程爲《經過例子學 Rust》的在線實驗版本,經過在線實驗一系列程序例子,一步步完成 Rust 編程語言的入門。歡迎在課程倉庫中參與修訂和完善,倉庫地址見 經過例子學 Rust - 在線實驗版。全部文檔內容版權跟隨中文及英文原文檔的版權(版權爲 MIT 協議 或 Apache 協議)。

知識點

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

  • 課程介紹
  • 如何編寫第一個程序
  • Hello World 程序詳解
  • 註釋
  • 格式化輸出

Hello World

咱們的第一個程序將打印傳說中的 "Hello World" 消息,下面是完整的程序代碼和編譯運行過程。

這是傳統的 Hello World 程序的源碼。首先,在實驗樓 WebIDE 中 /home/project 目錄下新建 hello.rs 文件,編寫如下代碼(以 // 開頭的註釋內容能夠沒必要輸入):

// 這是註釋內容,將會被編譯器忽略掉
// 能夠單擊那邊的按鈕 "Run" 來測試這段代碼 ->
// 若想用鍵盤操做,可使用快捷鍵 "Ctrl + Enter" 來運行

// 這段代碼支持編輯,你能夠自由地修改代碼!
// 經過單擊 "Reset" 按鈕可使代碼恢復到初始狀態 ->

// 這是主函數
fn main() {
    // 調用編譯生成的可執行文件時,這裏的語句將被運行。

    // 將文本打印到控制檯
    println!("Hello World!");
}

println! 是一個 宏(macros),能夠將文本輸出到控制檯(console)。

在實驗樓 WebIDE 終端中執行如下命令,使用 Rust 的編譯器 rustc 從源程序生成可執行文件:

$ cd /home/project
$ rustc hello.rs

使用 rustc 編譯後將獲得可執行文件 hello,使用如下命令來運行生成的文件 hello:

$ ./hello

執行後的結果以下所示:

動手試一試

請嘗試下在你的 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

執行結果以下所示:

因此 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 中後,編譯並運行的結果以下所示:

你能夠經過手動實現 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);
}

程序運行的結果以下所示:

fmt::Display 被實現了,而 fmt::Binary 沒有,所以 fmt::Binary 不能使用。 std::fmt 有不少這樣的 trait,它們都要求有各自的實現。這些內容將在 後面的 std::fmt 章節中詳細介紹。

動手試一試

檢驗上面例子的輸出,而後在示例程序中,仿照 Point2D 結構體增長一個複數結構體。使用同樣的方式打印,輸出結果要求是這個樣子:

Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }

測試實例:List

對一個結構體實現 fmt::Display,其中的元素須要一個接一個地處理到,這可能會很麻煩。問題在於每一個 write! 都要生成一個 fmt::Result。正確的實現須要處理全部的 Result。Rust 專門爲解決這個問題提供了 ? 操做符。

在 write! 上使用 ? 會像是這樣:

// 對 `write!` 進行嘗試(try),觀察是否出錯。若發生錯誤,返回相應的錯誤。
// 不然(沒有出錯)繼續執行後面的語句。
write!(f, "{}", value)?;

另外,你也可使用 try! 宏,它和 ? 是同樣的。這種寫法比較羅嗦,故再也不推薦, 但在老一些的 Rust 代碼中仍會看到。使用 try! 看起來像這樣:

try!(write!(f, "{}", value));

有了 ?,對一個 Vec 實現 fmt::Display 就很簡單了。新建 vector.rs 文件,編寫代碼以下:

use std::fmt; // 導入 `fmt` 模塊。

// 定義一個包含單個 `Vec` 的結構體 `List`。
struct List(Vec<i32>);

impl fmt::Display for List {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 使用元組的下標獲取值,並建立一個 `vec` 的引用。
        let vec = &self.0;

        write!(f, "[")?;

        // 使用 `v` 對 `vec` 進行迭代,並用 `count` 記錄迭代次數。
        for (count, v) in vec.iter().enumerate() {
            // 對每一個元素(第一個元素除外)加上逗號。
            // 使用 `?` 或 `try!` 來返回錯誤。
            if count != 0 { write!(f, ", ")?; }
            write!(f, "{}", v)?;
        }

        // 加上配對中括號,並返回一個 fmt::Result 值。
        write!(f, "]")
    }
}

fn main() {
    let v = List(vec![1, 2, 3]);
    println!("{}", v);
}

程序運行的結果以下:

動手試一試:

更改程序使 vector 裏面每一個元素的下標也可以打印出來。新的結果以下:

[0: 1, 1: 2, 2: 3]

格式化

咱們已經看到,格式化的方式是經過格式字符串來指定的:

  • format!("{}", foo) -> "3735928559"
  • format!("0x{:X}", foo) -> "0xDEADBEEF"
  • format!("0o{😮}", foo) -> "0o33653337357"

根據使用的參數類型是 X、o 仍是未指定,一樣的變量(foo)可以格式化成不一樣的形式。

這個格式化的功能是經過 trait 實現的,每種參數類型都對應一種 trait。最多見的格式化 trait 就是 Display,它能夠處理參數類型爲未指定的狀況,好比 {}。

新建 format2.rs 文件,編寫代碼以下:

use std::fmt::{self, Formatter, Display};

struct City {
    name: &'static str,
    // 緯度
    lat: f32,
    // 經度
    lon: f32,
}

impl Display for City {
    // `f` 是一個緩衝區(buffer),此方法必須將格式化後的字符串寫入其中
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' };
        let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' };

        // `write!` 和 `format!` 相似,但它會將格式化後的字符串寫入
        // 一個緩衝區(即第一個參數f)中。
        write!(f, "{}: {:.3}°{} {:.3}°{}",
               self.name, self.lat.abs(), lat_c, self.lon.abs(), lon_c)
    }
}

#[derive(Debug)]
struct Color {
    red: u8,
    green: u8,
    blue: u8,
}

fn main() {
    for city in [
        City { name: "Dublin", lat: 53.347778, lon: -6.259722 },
        City { name: "Oslo", lat: 59.95, lon: 10.75 },
        City { name: "Vancouver", lat: 49.25, lon: -123.1 },
    ].iter() {
        println!("{}", *city);
    }
    for color in [
        Color { red: 128, green: 255, blue: 90 },
        Color { red: 0, green: 3, blue: 254 },
        Color { red: 0, green: 0, blue: 0 },
    ].iter() {
        // 在添加了針對 fmt::Display 的實現後,請改用 {} 檢驗效果。
        println!("{:?}", *color)
    }
}

程序運行的結果以下:

在 fmt::fmt 文檔中能夠查看格式化 traits 一覽表和它們的參數類型。

動手試一試

爲上面的 Color 結構體實現 fmt::Display,應獲得以下的輸出結果:

RGB (128, 255, 90) 0x80FF5A
RGB (0, 3, 254) 0x0003FE
RGB (0, 0, 0) 0x000000

若是感到疑惑,可看下面兩條提示:

  • 你可能須要屢次列出每一個顏色,
  • 你可使用 :02 補零使位數爲 2 位。

實驗總結

本節實驗中咱們學習瞭如下的內容:

  • 課程介紹
  • 如何編寫第一個程序
  • Hello World 程序詳解
  • 註釋
  • 格式化輸出

還想繼續學習的小夥伴,點擊 《經過例子學 Rust》, 免費學!

相關文章
相關標籤/搜索