Rust学习笔记1

这是一份不错的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
View Code

而后编写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里还有几个文档,虽然暂时用不着但能够之后留着参考:

 

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

代码

相关文章
相关标签/搜索