本项目主要是用来验证Rust
模块化编程。
cargo new --bin csv_challenge
创建二进制项目。
cargo
默认使用了--bin
参数。
默认目录结构
.
├── Cargo.toml
├── src
│ └── main.rs
structopt
是基于clap
的基础上构建而成,简化了操作。
添加依赖
[dependencies]
structopt = "0.2"
structopt-derive = "0.2"
因为
structopt
是基于过程宏(Procedural Macro
)的,所以它依赖sructopt-derive
包。
创建src/opt.rs
use structopt_derive::*;
#[derive(StructOpt, Debug)]
#[structopt(name = "csv_challenge", about = "Usage")]
pub struct Opt {
#[structopt(help = "Input file")]
pub input : String,
#[structopt(help = "Column Name")]
pub column_name: String,
#[structopt(help = "Replcaement Column Name")]
pub replacement:String,
#[structopt(help ="Output file, stdout if not present")]
pub output: Option<String>,
}
在main.rs
中来引用Opt
。
mod opt;
use self::opt::Opt;
fn main(){
let opt = Opt::from_args();
}
添加src/core/read.rs
和src/core/write.rs
两个文件
- read.rs
use super::{Error, PathBuf,File,Read,Write};
pub fn load_csv(csv_file: PathBuf) -> Result<String,Error> {
let file = read(csv_file)?;
Ok(file)
}
pub fn write_csv(csv_data: &str, filename: &str) -> Result<(),Error> {
write(csv_data,filename)?;
Ok(())
}
fn read(path: PathBuf) -> Result<String,Error> {
let mut buffer = String::new();
let mut file = open(path)?;
file.read_to_string(&mut buffer)?;
if buffer.is_empty() {
return Err("input file missing")?
}
Ok(buffer)
}
fn open(path: PathBuf) -> Result<File, Error> {
let file = File::open(path)?;
Ok(file)
}
fn write(data: &str, filename : &str) -> Result<(), Error> {
let mut buffer = File::create(filename)?;
buffer.write_all(data.as_bytes())?;
Ok(())
}
- write.rs
use super::*;
pub fn replace_column(data: String, column: &str, replacement: &str) -> Result<String, Error> {
let mut lines = data.lines();
let headers = lines.next().unwrap();
let columns : Vec<&str> = headers.split(',').collect();
let column_number = columns.iter().position(|&e| e == column);
let column_number = match column_number {
Some(column) => column,
None => Err("column name doesn't exist in the input file")?,
};
let mut result = String::with_capacity(data.capacity());
result.push_str(&columns.join(","));
result.push('\n');
for line in lines {
let mut records : Vec<&str> = line.split(',').collect();
records[column_number] = replacement;
result.push_str(&records.join(","));
result.push('\n');
}
Ok(result)
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use super::load_csv;
#[test]
fn test_valid_load_csv() {
let filename = PathBuf::from("./input/challenge.csv");
let csv_data =load_csv(filename);
assert!(csv_data.is_ok());
}
}
Rust
对于二进制是不能添加集成测试的,因为二进制包只能独立使用,并不能对外提供可调用的函数,需要进行改造。
mod opt;
mod err;
mod core;
pub use self::opt::Opt;
pub use self::core::{
read::{load_csv,write_csv},
write::replace_column,
};
use csv_challenge::{
Opt,
{load_csv,write_csv},
replace_column,
};
这种
main.rs
配合lib.rs
的形式,是二进制包的最佳实践
#[cfg(test)]
mod test{
use std::path::PathBuf;
use std::fs;
use csv_challenge::{
{load_csv,write_csv},
replace_column,
};
#[test]
fn test_csv_challenge() {
let filename = PathBuf::from("./input/challenge.csv");
let csv_data = load_csv(filename).unwrap();
let modified_data = replace_column(csv_data,"City","Beijing").unwrap();
let output_file = write_csv(&modified_data, "output/test.csv");
assert!(output_file.is_ok());
fs::remove_file("output/test.csv");
}
}
#![feature(test)]
extern crate test;
use test::Bencher;
use std::path::PathBuf;
use csv_challenge::{
Opt,
{load_csv, write_csv},
replace_column,
};
#[bench]
fn bench_read_100times(b: &mut test::Bencher) {
b.iter(|| {
let n = test::black_box(100);
(0..n).fold(0, |_,_|{test_load_csv();0})
});
}
fn test_load_csv() {
let filename = PathBuf::from("./input/challenge.csv");
load_csv(filename);
}
注意:要使用基准测试,必须启用
#![feature(test)]
。但是只能在rust nightly
版本中使用。
- 安装
nightly
版本
rustup toolchian install nightly
rustup default nightly
- 运行基准测试
cargo bench
基准测试运行结果
Running target/release/deps/file_op_bench-4a13631faeeecf0a
running 1 test
test bench_read_100times ... bench: 2,019,512 ns/iter (+/- 1,480,889)
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out