Part 1: Make the tests compile

配置好之后尝试运行 cargo test,发现编译错误:

1
2
3
4
5
error[E0433]: failed to resolve: use of undeclared crate or module `assert_cmd`
--> tests\tests.rs:1:5
|
1 | use assert_cmd::prelude::*;
| ^^^^^^^^^^ use of undeclared crate or module `assert_cmd`

显然是缺少相应的 crate,在 cargo.toml 里面加上。

1
2
3
[dev-dependencies]
assert_cmd = "0.11.0"
predicates = "1.0.0"

dev-dependenciesdependencies 的区别可以看官方文档

再次运行 cargo test,发现编译错误:

1
2
3
4
5
error[E0432]: unresolved import `kvs::KvStore`
--> tests\tests.rs:2:5
|
2 | use kvs::KvStore;
| ^^^^^^^^^^^^ no `KvStore` in the root

也就是还没有实现对应的接口,先在 kvs/src/lib.rs 中添加对应的接口,内容直接 panic!() 就行(也可以用 unimplemented!,不过有点麻烦)。

观察 tests.rs,根据里面的调用写对应的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub struct KvStore {}

impl KvStore {
pub fn new() -> Self {
unimplemented!();
}

pub fn set(&mut self, _key: String, _value: String) {
unimplemented!();
}

pub fn get(&self, _key: String) -> Option<String> {
unimplemented!();
}

pub fn remove(&mut self, _key: String) {
unimplemented!();
}
}

然后 cargo test --no-run,发现可以通过编译。


Part 2: Accept command line arguments

使用 clap crate 进行参数解析。

参考 docs.rs 上的教程,首先 cargo add clap --features derive 启用功能,然后依次添加对应的 Subcommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/lib.rs
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[command(subcommand)]
pub command: Command,
}

#[derive(Subcommand)]
pub enum Command {
Set { key: String, value: String },
Get { key: String },
Rm { key: String },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/bin/kvs.rs
use clap::Parser;
use kvs::{Args, Command, KvStore};

fn main() {
let mut store = KvStore::new();
let args = Args::parse();
match args.command {
Command::Set { key, value } => {
store.set(key, value);
}
Command::Get { key } => {
store.get(key);
}
Command::Rm { key } => {
store.remove(key);
}
}
}

然后使用 cargo test cli 进行测试。


Part 3: Cargo environment variables

cargo.toml 中设置 clap crate 的一些默认参数。一个简单例子:

1
2
3
4
5
6
[package]
name = "kvs"
version = "0.1.0"
authors = ["MizukiCry <YuukaC@outlook.com>"]
description = "An in-memory key-value store for study."
edition = "2021"

Part 4: Store values in memory

实现 KvStore 的接口通过剩余测试,其实也就是套 HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pub struct KvStore {
store: HashMap<String, String>,
}

impl KvStore {
pub fn new() -> Self {
Self {
store: HashMap::new(),
}
}

pub fn set(&mut self, key: String, value: String) {
self.store.insert(key, value);
}

pub fn get(&self, key: String) -> Option<String> {
self.store.get(&key).cloned()
}

pub fn remove(&mut self, key: String) {
self.store.remove(&key).unwrap();
}
}

Part 5: Documentation

src/lib.rs 内所有内容加上注释。

顺便将解析参数的内容移到了 src/bin/kvs.rs 中,更加方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/lib.rs
#![deny(missing_docs)]
//! `kvs` is a crate that privodes an in-memory key-value store.

use std::collections::HashMap;

/// An in-memory key-value store.
///
/// # Examples
///
/// ```rust
/// use kvs::KvStore;
///
/// let mut store = KvStore::new();
/// store.set(String::from("Ayumu"), String::from("Uehara"));
/// assert_eq!(Some(String::from("Uehara")), store.get(String::from("Ayumu")));
/// store.remove(String::from("Ayumu"));
/// assert_eq!(None, store.get(String::from("Ayumu")));
/// ```
pub struct KvStore {
store: HashMap<String, String>,
}

impl KvStore {
/// Returns a `KvStore` instance.
pub fn new() -> Self {
Self {
store: HashMap::new(),
}
}

/// Inserts a new key-value pair into the store.
pub fn set(&mut self, key: String, value: String) {
self.store.insert(key, value);
}

/// Query the value by the given key. Returns `None` if the key doesn't exist.
pub fn get(&self, key: String) -> Option<String> {
self.store.get(&key).cloned()
}

/// Remove a key in the store if exists.
pub fn remove(&mut self, key: String) {
self.store.remove(&key).unwrap();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/bin/kvs.rs
use clap::{Parser, Subcommand};
use kvs::KvStore;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[command(subcommand)]
command: Command,
}

#[derive(Subcommand)]
enum Command {
Set { key: String, value: String },
Get { key: String },
Rm { key: String },
}

fn main() {
let mut store = KvStore::new();
let args = Args::parse();
match args.command {
Command::Set { key, value } => {
store.set(key, value);
}
Command::Get { key } => {
store.get(key);
}
Command::Rm { key } => {
store.remove(key);
}
}
}

完成之后可以通过 cargo doc --open 查看文档,cargo test --doc 单独测试。


Part 6: Ensure good style with clippy and rustfmt

安装 clippyrustfmt 工具:

1
2
rustup component add clippy
rustup component add rustfmt

接着运行 cargo clippycargo fmt 进行检查,并改进源代码。

1
2
3
4
5
6
// src/lib.rs
impl Default for KvStore {
fn default() -> Self {
Self::new()
}
}

Extension 1: structopt

使用 structopt crate 代替 clap crate 再来一遍。感觉好烦,不写了