這是一份不錯的rust教程,目前包括4個block和4個project。所有完成後能夠用rust實現一個簡單的key-value存儲引擎。html
注意:Windows下rust貌似會遇到一些bug,強烈建議使用Linux來開發java
Building Block1
一開始就是Hello World啦......經過實現一個簡單的命令行程序來體驗一下rustc++
好比咱們但願程序能得到命令行參數git
use std::env; fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args); }
運行結果:
F:\My Drive\19fall\talent-plan\rust\building-blocks\bb1\src>main.exe 11 22
["main.exe", "11", "22"]
這一段看起來和c++差很少......(其實感受rust比go好理解多了...)github
- println!結尾的歎號!表示調用了一個Rust宏。若是是調用函數,應該輸入println
可是一個複雜的cli程序(好比Linux中的ls),命令行參數是很複雜的。好比咱們想給寫個help(好比ls -h)供用戶參考,該怎麼辦呢?咱們可使用rust的clap庫來實現。api
首先須要定義一個yml,裏面定義命令行參數的格式,保存爲/src/cli.ymlapp


name: myapp version: "1.0" author: Kevin K. <kbknapp@gmail.com> about: Does awesome things args: - config: short: c long: config value_name: configval help: Sets a custom config file takes_value: true - INPUT: help: Sets the input file to use required: true index: 1 - verbose: short: v multiple: true help: Sets the level of verbosity subcommands: - test: about: controls testing features version: "1.3" author: Someone E. <someone_else@other.com> args: - debug: short: d help: print debug information
而後編寫rust程序,保存爲/src/main.rs:less
1 #[macro_use] 2 extern crate clap; 3 use clap::App; 4 5 fn main() { 6 println!("Hello, world"); 7 let yaml = load_yaml!("cli.yml"); 8 let m = App::from_yaml(yaml).get_matches(); 9 10 if let Some(configval) = m.value_of("config"){ 11 match configval{ 12 "c1" => println!("config 1111"), 13 "c2" => println!("config 2222"), 14 "c3" => println!("config 3333"), 15 _ => println!("what did you config?") 16 } 17 } else { 18 println!("--config is not assigned"); 19 } 20 21 if let Some(inputval) = m.value_of("INPUT"){ 22 println!("{:?}", inputval); 23 } else { 24 println!("INPUT is not assigned"); 25 } 26 }
- 這裏crate是一個二進制或庫項目
- match至關於C語言中的switch語句
- if let xx=yy {} else {} 是一個經常使用的能夠處理異常(好比用戶沒有提供這個參數)的寫法
可是若是直接用rustc來運行上面的程序會報錯噢:ide
F:\My Drive\19fall\talent-plan\rust\building-blocks\bb1\src>rustc main.rs error[E0463]: can't find crate for `clap` --> clapusage.rs:2:1 | 2 | extern crate clap; | ^^^^^^^^^^^^^^^^^^ can't find crate error: aborting due to previous error For more information about this error, try `rustc --explain E0463`.
這是由於本地默認尚未安裝clap這個庫,須要手動告訴rust來安裝這個庫(相似pip install一下)。這一點和C++不同哦。函數
爲了方便起見咱們改用cargo來編譯運行rust。cargo是rust的構建系統和包管理器,能夠幫咱們自動完成下載安裝依賴庫的工做。爲了使用cargo,咱們須要一開始就用 cargo new newproj 來新建項目。新建好的項目文件夾中會有cargo.toml文件,咱們打開該文件,加入如下語句來聲明使用了clap中的yaml庫:
[dependencies.clap] features = ["yaml"]
而後使用cargo build來編譯項目,使用cargo run來編譯+運行,使用cargo clean來清除上次編譯的結果(有點像Makefile的做用)。這裏咱們cargo build,而後進入/target/debug/文件夾,就能夠看到編譯好的可執行文件啦。
運行結果以下,能夠看到既能夠打印help,也能夠處理命令行輸入:
tidb@pcserver:/mnt/toshiba/talent-plan/rust/building-blocks/bb1$ ./target/debug/bb1 -c c1 fff Hello, world config 1111 "fff" tidb@pcserver:/mnt/toshiba/talent-plan/rust/building-blocks/bb1$ ./target/debug/bb1 --help Hello, world myapp 1.0 Kevin K. <kbknapp@gmail.com> Does awesome things USAGE: bb1 [FLAGS] [OPTIONS] <INPUT> [SUBCOMMAND] FLAGS: -h, --help Prints help information -V, --version Prints version information -v Sets the level of verbosity OPTIONS: -c, --config <configval> Sets a custom config file ARGS: <INPUT> Sets the input file to use SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) test controls testing features
處理好了命令行,可能某一天PM想讓程序猿再加個讀取環境變量的功能。還好系統仍是提供了庫函數(因此仍是調包大法好?)
1 fn main() { 2 println!("Hello, world!"); 3 use std::env; 4 5 let key = "HOME"; 6 match env::var_os(key) { 7 Some(val) => println!("{}: {:?}", key, val), 8 None => println!("{} is not defined in the environment.", key) 9 } 10 } 11 12 運行結果: 13 tidb@pcserver:/mnt/toshiba/talent-plan/rust/building-blocks/bb1env$ cargo run 14 Finished dev [unoptimized + debuginfo] target(s) in 0.00s 15 Running `target/debug/bb1env` 16 Hello, world! 17 HOME: "/home/tidb"
- 注意6-9行裏,Some(T)和None來自於一種枚舉類型Option<T>。對於env::var_os(key)的返回值,Some(val)表示結果是某個存在的值,並把它存到val變量中;而None表示返回結果是空的(至關於c語言中的NULL)。這樣作的好處是,好比咱們在函數返回的時候獲得一個Option<i8>類型(多是Some(i8),也多是None),在把它轉換回i8類型時就已經解決了值爲空的狀況(好比用6-9行的match),以後的i8類型就必定不爲空了。這樣就避免了c語言裏空指針可能帶來的問題。
錯誤處理
用戶有的時候是很皮的(程序猿也是),因此程序不可避免會遇到一些異常狀況。在java和c++裏咱們能夠用 try...catch... / throw 來處理異常,rust也提供了相似的機制。好比上次改TiKV config的時候就用到了。原教程給的例子不大好...這裏咱們本身寫一個:
1 use std::env; 2 3 enum ErrTypes{ 4 Err111, 5 // Err222, 6 } 7 8 fn getargs(args: Vec<String>) -> Result<String, ErrTypes>{ 9 match args.get(1) { 10 Some(_v) => Ok(_v.to_string()), 11 None => Err(ErrTypes::Err111) 12 } 13 } 14 15 fn main() { 16 println!("Hello, world!"); 17 let args: Vec<String> = env::args().collect(); 18 19 let val=getargs(args); 20 match val{ 21 Ok(_v) => println!("OK, val == {:?}", _v), 22 Err(_e) => println!("Error!!!!!") 23 } 24 }
這段代碼的含義仍是很易懂的(雖然寫的時候但是debug了半天qwq),就是檢測第二個命令行參數是否存在(第一個默認是調用該程序的cmd,即相似於"C:\command.com"這種)。
這裏咱們用Result進行了異常處理。Result也是一種枚舉類型,定義以下:
enum Result<T, E> { Ok(T), Err(E), }
這裏T和E都是泛型類型參數。好比在上面的代碼中,getargs函數的返回值是Result<String, ErrTypes>類型,表示函數執行成功的時候應該返回一個String,而失敗的時候返回ErrTypes(咱們本身定義的一個錯誤類型)。[Ref]
咱們運行一下看看:
tidb@pcserver:/mnt/toshiba/talent-plan/rust/building-blocks/bb1err$ ./target/debug/bb1err www Hello, world! OK, val == "www" tidb@pcserver:/mnt/toshiba/talent-plan/rust/building-blocks/bb1err$ ./target/debug/bb1err Hello, world! Error!!!!!
另外block1裏還有幾個文檔,雖然暫時用不着但能夠之後留着參考:
- cargo manifest format:介紹 Cargo.toml 的結構。前面clap那個例子裏咱們就修改了這個文件來支持外部的庫
- cargo environment variables:介紹cargo會用到的一些環境變量。
- rust documentation:介紹如何編寫rust代碼的文檔(以代碼註釋的形式)
Project1
第一個project是一個簡單的key-value store ,其實就是調用HashMap+處理一下命令行輸入輸出。那咱們就開始叭
Part1 rust中的HashMap
打開空白的kv.rs,能夠看到裏面已經有了一個半成品,咱們直接往裏面填空就能夠啦。這個文件裏定義了一個KvStore結構體,pub struct{}裏面能夠定義結構體成員變量(這裏沒有成員變量),impl KvStore{}裏面能夠定義結構體成員方法。
單純操做hashmap仍是很容易的...可是在這裏面咱們能夠學習一個rust函數的操做
在這個文件裏能夠看到不少函數都會有一些奇怪的參數,有的是&self,有的是&mut self。另外像get和new函數還要有返回值。
- &表示引用,它容許你使用值但不獲取其全部權,意義相似於c++中的傳參數指針。但rust中,函數引用來的變量在該函數中是不可被修改的。
- mut表示該變量是可更改的。能夠用& mut varname建立一個可變引用。但對於同一個變量,同一時間只能有一個可變引用,或者多個普通的不可變引用。
- 在《rust程序設計語言》的「認識全部權」一節中,詳細說明了全部權和引用的概念。
- ::是運算符,表示指定namespace下的特定函數,也和c++同樣
- kv.rs中能夠理解爲定義了KvStore這個結構體的成員函數和方法。
- impl中定義了一個new()函數,做用是初始化並返回一個KvStore結構體。它有點像c++中的構造函數,但rust中必須自行定義,由於rust中其實沒有類的概念。
- impl中定義了KvStore結構體的三個方法set、get、remove。方法的第一個參數老是self,它表明調用該方法的結構體實例(這裏就是一個KvStore了,由於這三個方法都是做用在KvStore類型的結構體上的)。&self表示不可變的引用,而&mut self表示可變的引用。 一樣由於rust中沒有類的概念,因此須要搞一個self來接收調用該方法的結構體實例。
- 函數若是須要返回一個值,直接寫這個值便可,不須要return關鍵字。返回的這個值末尾也不加分號
- .cloned()我也不知道啥意思...就先這麼着吧 QAQ
- set中的key和value不須要引用,是函數就這樣要求的,否則編譯會報錯...
Part2 處理命令行輸入
這部分是在kvs.rs中進行的。首先咱們要use相關的庫:clap(用於解析命令行參數)和exit(用於退出時命令行返回值)
根據題目要求,這個程序須要實現如下參數:
- kvs set <KEY> <VALUE> Set the value of a string key to a string
- kvs get <KEY> Get the string value of a given string key
- kvs rm <KEY> Remove a given key
- kvs -V Print the version
爲了讓代碼更加整潔,咱們像上面的例子同樣,把命令的定義寫在yml裏,而後load_yaml!()來讀取這些命令。set、get、rm做爲subcommand,而Version做爲args。
鑑於純內存的hashmap反正退出程序以後東西都是會丟失的...就不implement命令行啦
Part3 組裝起來吧!
如今咱們的文件結構長這樣:
✉ project-1 |--✉ src | |--✉ bin | | |-- cli.yml | | |-- kvs.rs | | | |--lib.rs | |--kv.rs | |--✉ tests | |-- tests.rs | |-- Cargo.toml |-- project.md
前面寫好了kv.rs來定義KvStore結構體,kvs.rs定義了main函數來處理命令行輸入
lib.rs很短...就兩行,用於把KvStore包含進來,至關於c++中.h的做用。(詳細可參考《rust程序設計語言》的「模塊系統」一節)
pub use kv::KvStore; mod kv;
所有組裝好以後就能夠啦!能夠cargo test來測試一下結果
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out