• 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 Drive19fall alent-plan ustuilding-blocksb1src>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 Drive19fall	alent-plan
    ustuilding-blocksb1src>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

    代码

  • 相关阅读:
    程序员的希波克拉底誓言[精华]
    怎样成为优秀的软件模型设计者
    C#中Delegate浅析与思考
    程序员是一个美好的职业[精华]
    hdu 1421(搬寝室)
    hdu 4022(map一对多)
    hdu 1114(完全背包)
    hdu 1159(最长公共子序列)
    hdu 2844(多重背包)
    hdu 1257(最长递增子序列)
  • 原文地址:https://www.cnblogs.com/pdev/p/11432913.html
Copyright © 2020-2023  润新知