Rust學習筆記1


這是一份不錯的rust教程,目前包括4個block和4個project。全部完成后可以用rust實現一個簡單的key-value存儲引擎。

注意:Windows下rust貌似會遇到一些bug,強烈建議使用Linux來開發

 

Building Block1

一開始就是Hello World啦......通過實現一個簡單的命令行程序來體驗一下rust

比如我們希望程序能獲得命令行參數

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好理解多了...)

  • println!結尾的嘆號!表示調用了一個Rust宏。如果是調用函數,應該輸入println

 

但是一個復雜的cli程序(比如Linux中的ls),命令行參數是很復雜的。比如我們想給寫個help(比如ls -h)供用戶參考,該怎么辦呢?我們可以使用rust的clap庫來實現。

首先需要定義一個yml,里面定義命令行參數的格式,保存為/src/cli.yml

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
View Code

然后編寫rust程序,保存為/src/main.rs:

 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來運行上面的程序會報錯噢:

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里還有幾個文檔,雖然暫時用不着但可以以后留着參考:

 

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

代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM