【Rust5】常用工具包



2022年09月17日    Author:Guofei

文章归类: 语言    文章编号: 11205

版权声明:本文作者是郭飞。转载随意,但需要标明原文链接,并通知本人
原文链接:https://www.guofei.site/2022/09/17/rust5.html


rand随机

rand = "0.8.3"

use rand::Rng;


// 每个线程都有一个初始化的生成器
let mut rng = rand::thread_rng();

// 整数范围是该类型的范围
let n1: u8 = rng.gen();
let n2: u16 = rng.gen();
// 浮点数的范围是0到1,但不包括1
let f1: f64 = rng.gen();

生成一个范围内的随机数

let f2_rnd: f64 = rng.gen_range(0.0..10.0);
let i2_rnd: i32 = rng.gen_range(0..10);

指定分布

// 整数
Uniform::from(1..7);
// 浮点数
Uniform::from(1.0..7.0);


// 更复杂的类型用 rand_distr
use rand_distr::{Normal, Binomial, Gamma};

// mean 2, standard deviation 3
let mut normal = Normal::new(2.0, 3.0).unwrap();
let val = normal.sample(&mut rand::thread_rng());

案例:随机生成密码

use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                        abcdefghijklmnopqrstuvwxyz\
                        0123456789)(*&^%$#@!~";
const PASSWORD_LEN: usize = 30;
let mut rng = rand::thread_rng();

let password: String = (0..PASSWORD_LEN)
    .map(|_| {
        let idx = rng.gen_range(0..CHARSET.len());
        CHARSET[idx] as char
    })
    .collect();

sort

vec.sort 可以用于 int, &str 但不能用于 float

let mut vec = vec![1, 5, 10, 2, 15];
let mut vec = vec!["aa", "ab", "b"];

vec.sort();
print!("{:?}", vec);

下面这种写法很通用

let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0];
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());

案例:对 vec<struct> 排序

struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn new(name: String, age: u32) -> Self {
        Person { name, age }
    }
}

fn main() {
    let mut people = vec![
        Person::new("Zoe".to_string(), 25),
        Person::new("Al".to_string(), 60),
        Person::new("John".to_string(), 1),
    ];

    people.sort_by(|a, b| b.age.cmp(&a.age));
}

命令行工具 clap

// clap = "*"
use clap::{Arg, App};

fn main() {
    let matches = App::new("app名称")
        .version("0.0.1")
        .author("guofei9987 <me@guofei.site>")
        .about("关于,这个app是干什么的")
        .arg(Arg::with_name("file")
            .short('f')
            .long("file")
            .takes_value(true)
            .help("A cool file"))
        .arg(Arg::with_name("num")
            .short('n')
            .long("number")
            .takes_value(true)
            .help("Five less than your favorite number"))
        .get_matches();

    let myfile = matches.value_of("file").unwrap_or("input.txt");
    println!("The file passed is: {}", myfile);

    let num_str = matches.value_of("num");
    match num_str {
        None => println!("No idea what your favorite number is."),
        Some(s) => {
            match s.parse::<i32>() {
                Ok(n) => println!("Your favorite number must be {}.", n + 5),
                Err(_) => println!("That's not a number! {}", s),
            }
        }
    }
}

使用

# 正经使用
cargo run -- -f myfile.txt -n 251

# -V 和 --version 显示app名称+版本
cargo run -- -V

# -h, --help 显示详细的帮助信息
cargo run -- -h

ANSI:彩色命令行

// ansi_term = "*"
use ansi_term::Colour;
use ansi_term::Style;

fn main() {
    println!("This is {}, {}, {} and {}. This is {}.",
             Colour::Red.paint("red"),
             Colour::Blue.paint("blue"),
             Colour::Green.paint("green"),
             Colour::Yellow.paint("yellow"),
             Style::new().bold().paint("bold"),
    );
}

time

时间差

let start = Instant::now();
thread::sleep(Duration::from_secs(1));
let duration = start.elapsed();
// duration 可以转化为很多种数字格式
// duration.as_secs_f32(); // 返回 f32,

now

// std::time::Duration 重复了,所以做个别名
use chrono::Duration as Duration2;

let now = Utc::now();
now.checked_add_signed(Duration2::days(-1))
// 还可以用checked_sub_signed
// Duration2:: 可以是:
// days, weeks,
// hours, minutes, milliseconds, microseconds

时区(略)

生成时间对象

let now = Utc::now();

let date_time = chrono::NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44);

时间对象格式转字符串

let now = Utc::now();

// 标准格式时间
let time_str = format!("{}-{:02}-{:02} {:02}:{:02}:{:02}",
                       now.year(), now.month(), now.day(),
                       now.hour(), now.minute(), now.second());



// 其它格式时间
let (is_common_era, year) = now.year_ce();
println!(
   "The current UTC date is {}-{:02}-{:02} {:?} ({})",
   year,
   now.month(),
   now.day(),
   now.weekday(),
   if is_common_era { "CE" } else { "BCE" }
);

let (is_pm, hour) = now.hour12();
println!(
   "The current UTC time is {:02}:{:02}:{:02} {}",
   hour,
   now.minute(),
   now.second(),
   if is_pm { "PM" } else { "AM" }
);

时间对象转 timestamp

now.timestamp()

字符串转日期

let no_timezone = chrono::NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")
    .unwrap();

sleep

use std::thread;
use std::time::Duration

thread::sleep(Duration::from_secs(2));

正则

[dependencies]
regex = "*"
// 编译正则
let re = Regex::new("\\b[\\w\\-\\+\\.]+@[\\w\\-\\+\\.]+\\.\\w{2,18}\\b").unwrap();
let text = "aaa  me@guofei.site guofei9987@foxmail.com";


// 是否有匹配,
let res: bool = re.is_match(text);
// true


// 单匹配
let mut res = Vec::new();
match re.captures(text) {
  // 这里面的序号0代表整体,序号1代表分组1
    Some(i) => { res.push(String::from(&i[0])) }
    None => {}
}




// 多匹配
let mut res = Vec::new();
for cap in re.captures_iter(text) {
    res.push(String::from(&cap[0]));
}


// 匹配并替换
let text_new = re.replace_all(text, "你");

关于分组运算

// 编译正则,定义老公两个组,分别命名为 user_name 和 domain
let re = Regex::new("\\b(?P<name>[\\w\\-\\+\\.]+)@[\\w\\-\\+\\.]+\\.(?P<domain>\\w{2,18})\\b").unwrap();
let text = "aaa  me@guofei.site guofei9987@foxmail.com";
let text2 = "aaaaaaa";


// 单匹配:上面用 index 方便一些,如果想用name:
// re.captures(text) 是一个 `<Option<Captures<"user_name":Option>>>` 这种嵌套
let mut res: Vec<String> = Vec::new();
match re.captures(text) {
    Some(i) => {
        res.push(String::from(i.name("user_name").unwrap().as_str()));
    }
    None => {}
}

// 还可以这样,但是匹配不到的时候直接 Panic 了
let res: &str = cap.unwrap().name("user_name").unwrap().as_str();


// 多匹配
let mut res = Vec::new();
for cap in re.captures_iter(&text) {
    match cap.name("user_name") {
        None => {}
        Some(i) => { res.push(String::from(i.as_str())) }
    }
}


// 替换
let text_new = re.replace_all(text, "$user_name/$domain");

正则语法

.             # 除新行以外的任何字符(包括带有s标志的新行)
\d            # 数字 (\p{Nd})
\D            # 非数字

\pN           # 单字母名称Unicode字符类
\PN           # Negated(非) 单字母名称Unicode字符类

\p{Greek}     # Unicode字符类(常规类别或脚本)
\P{Greek}     # negated(非) Unicode字符类(常规类别或脚本)
[xyz]         # 匹配x、y或z(并集)的字符类。
[^xyz]        # 匹配 除x、y和z以外的 任何字符的字符类。
[a-z]         # 匹配A-z范围内任何字符的字符类。
[[:alpha:]]   # ASCII 字符类 ([A-Za-z])
[[:^alpha:]]  # Negated(非) ASCII 字符类 ([^A-Za-z])
[x[^xyz]]     # ???嵌套/分组字符类(匹配除y和z以外的任何字符)
[a-y&&xyz]    # 交集(匹配x或y)
[0-9&&[^4]]   # 使用交集和求反的减法(匹配0-9,4除外)
[0-9--4]      # 直接减法            (匹配0-9,4除外)
[a-g~~b-h]    # 对称差异(范围异或)(仅匹配“a”和“h”)
[\[\]]        # 字符类中的转义(匹配[ 或 ])
[[:alnum:]]        # alphanumeric ([0-9A-Za-z])
[[:alpha:]]        # alphabetic ([A-Za-z])
[[:ascii:]]        # ASCII ([\x00-\x7F])
[[:blank:]]        # blank ([\t ])
[[:cntrl:]]        # control ([\x00-\x1F\x7F])
[[:digit:]]        # digits ([0-9])
[[:graph:]]        # graphical ([!-~])
[[:lower:]]        # lower case ([a-z])
[[:print:]]        # printable ([ -~])             可打印
[[:punct:]]        # punctuation ([!-/:-@\[-`{-~]) 标点符号,不包含汉字标点
[[:space:]]        # whitespace ([\t\n\v\f\r ])    空白
[[:upper:]]        # upper case ([A-Z])
[[:word:]]         # word characters ([0-9A-Za-z_])
[[:xdigit:]]       # hex digit ([0-9A-Fa-f])       十六进制数字

参考:https://blog.csdn.net/wsp_1138886114/article/details/116519242 (rust的正则和python的正则有些区别,功能更多)

IO

include_str&include_bytes

include_str!include_bytes! 它在编译时将文件的内容作为字符串直接嵌入到代码中。

  • 优点
    • 无需运行时文件IO,从而减少开销
    • 简化部署: 文件就在编译后的文件中了。1)只需分发单个可执行文件。 2)无需担心路径和位置问题。 3)从而规避文件不存在、文件权限问题带来的 bug
    • 提高应用安全性:文件内容是嵌入在代码中的,不能被随意访问、修改
  • 缺点
    • 如果文件很大:会增加编译时间、增加内存消耗、增加程序启动时间

返回的类型是 &str, &[u8; ?]

std::fs 读取和写入

参考:

https://llever.com/rust-cookbook-zh/file/read-write.zh.html

https://blog.csdn.net/feiyanaffection/article/details/125575331?spm=1001.2014.3001.5502

读文件

let content = "hello, world!";
let filename = "output.txt";

// 最简易的方法

// 读
let data_bytes: Vec<u8> = fs::read(filename)
    .map_err(|e| e.to_string())?;
// 写
fs::write(filename2, data_bytes)
    .map_err(|e| e.to_string())?;

// 如果是文本文件,还可以直接读到 String 
let data_string: String = fs::read_to_string("file.txt")
    .map_err(|e| e.to_string())?;


// 使用 buffer。好处:可以预先分配内存,性能可能更高些。
let mut file = fs::File::open(filename)
    .map_err(|e| e.to_string())?;
let mut buf = vec![0; 100]; // 一次读入的数量,不会比它大
// read_num: 这次读入的字节数
let read_num = file.read(&mut buf).
    map_err(|e| e.to_string())?;

// 使用 buffer 读到 String
let mut file = fs::File::open(filename)
    .map_err(|e| e.to_string())?;
let mut buf = String::new();
file.read_to_string(&mut buf)
    .map_err(|e| e.to_string())?;




// 逐行读取
let mut file = std::fs::File::open("new_file.txt").unwrap();
let read = BufReader::new(file);
for line in read.lines() {
    let line = line.unwrap();
    println!("{}", line);
}

// 追加写
let mut file = std::fs::OpenOptions::new()
    .write(true) // 写入
    .create(true)// 或者 .create_new(true) :如果文件存在则报错
    .append(true) // true 是追加写,false 是覆盖写
    .open(filename).unwrap();
let n = file.write(content.as_bytes()).unwrap(); // 实际写入的字节数

目录

use std::{env, fs};

fn main() {
    let current_dir = env::current_dir().unwrap();
    println!(
        "Entries modified in the last 24 hours in {:?}:",
        current_dir
    );

    for entry in fs::read_dir(current_dir).unwrap() {
        let entry = entry.unwrap();
        let path = entry.path();

        let metadata = fs::metadata(&path).unwrap();
        let last_modified = metadata.modified().unwrap().elapsed().unwrap().as_secs();

        if last_modified < 24 * 3600 && metadata.is_file() {
            println!(
                "Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}",
                last_modified,
                metadata.permissions().readonly(),
                metadata.len(),
                path.file_name().ok_or("No filename").unwrap()
            );
        }
    }
}

调用 cmd

use std::process::{Command, Output};

fn main() {
    let res = Command::new("ls").arg("-l").output();

    match res {
        Ok(output) => {
            println!("{}", output.status.success());
            println!("{}", String::from_utf8(output.stdout).unwrap());
        }
        Err(_) => { println!("command 出错"); }
    }
}

更多:https://llever.com/rust-cookbook-zh/os/external.zh.html

zip

下面的代码:

  • 先读取文件到字节流
  • 然后用 zip 读取字节流,若干处理后生成一个新的字节流,
  • 然后把字节流保存到文件
let filename = "./files/file.zip";
let filename_new = "./files/file_new.zip";

// 读取文件到字节流
let data_bytes = fs::read(filename).unwrap();
// 从字节流读取 zip 文件
let cursor = Cursor::new(data_bytes);
let mut archive = zip::ZipArchive::new(cursor).unwrap();

// 新文件
let mut data_bytes_new: Vec<u8> = Vec::new();
let mut cursor_new = Cursor::new(&mut data_bytes_new);
let mut zip_writer = zip::ZipWriter::new(&mut cursor_new);

for i in 0..archive.len() {
    let mut file = archive.by_index(i).unwrap();
    let filename: String = file.name().to_string();

    let options = zip::write::FileOptions::default()
        .compression_method(file.compression()) // 还可以是别的值,例如 zip::CompressionMethod::Stored
        .unix_permissions(file.unix_mode().unwrap_or(0o755));

    if file.is_file() {
        // 读原始文件
        let mut buffer: Vec<u8> = Vec::new();
        file.read_to_end(&mut buffer).unwrap();

        // 新建一个文件
        zip_writer.start_file(filename, options).unwrap();
        // 写入
        zip_writer.write_all(&buffer).unwrap();
    } else if file.is_dir() {
        //     创建目录
        zip_writer.add_directory(filename, options).unwrap();
    }
}
zip_writer.finish().unwrap();

drop(zip_writer); // 释放 zip_writer,从而释放 cursor_new 的借用状态
// 重置 cursor_new 以获取完整的 ZIP 字节流
cursor_new.seek(SeekFrom::Start(0)).unwrap();

// 得到字节流
let data_bytes_new = cursor_new.into_inner();

// 写入
fs::write(filename_new, data_bytes_new).unwrap();

  • unix(包括Linux、macOS、IOS、Android) 的文件系统使用的是正斜线,windows 使用反斜线
  • 一般情况下,为了保证跨平台兼容性,都是使用正斜线。但偶尔有人不遵守这个约定
  • let filename: String = file.mangled_name().as_path().display().to_string(); 会根据当前操作系统来调整这个斜线
  • let filename: String = file.name().to_string(); 不会调整这个斜线(原始 zip 文件中是什么,这里就是什么)

下面的代码:读取一个 zip 文件,然后遍历它,把每个文件的内容都放到新的 zip 文件中

// zip = "*"

let filename = "files/file.xlsx";
let filename_new = "files/file_new.xlsx";
// 读取
let zip_file = fs::File::open(filename).unwrap();
let mut archive = zip::ZipArchive::new(zip_file).unwrap();

// 写入
let zip_file_new = fs::OpenOptions::new()
    .create(true).write(true).truncate(true)
    .open(filename_new).unwrap();
let mut zip_writer = zip::ZipWriter::new(zip_file_new);


for i in 0..archive.len() {
    let mut file = archive.by_index(i).unwrap();
    let filename = file.mangled_name();
    let options = zip::write::FileOptions::default()
        .compression_method(zip::CompressionMethod::Stored)
        .unix_permissions(file.unix_mode().unwrap_or(0o755));

    if file.is_file() {
        // 读
        let mut buffer: Vec<u8> = Vec::new();
        file.read_to_end(&mut buffer).unwrap();

        // 新建一个文件
        zip_writer.start_file(filename.as_path().display().to_string(), options).unwrap();
        // 写入
        zip_writer.write_all(&buffer).unwrap();
    } else if file.is_dir() {
        //     创建目录
        zip_writer.add_directory(filename.as_path().display().to_string(), options).unwrap();
    }
}

// 结束并关闭
zip_writer.finish().unwrap();

csv(待补充)

https://llever.com/rust-cookbook-zh/encoding/csv.zh.html

Json

说明

  • 可以把 struct 保存为 json
  • 配合 include_str! 可以编译时把文件内容放入内存

如何把一个 struct 保存为 json

serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: usize,
}
let p1 = Person {
    name: "张三".to_string(),
    age: 10,
};

// 序列化
let serialized:String = serde_json::to_string(&p1).unwrap();

// 反序列化
let p_read: Person = serde_json::from_str(&serialized).unwrap();

https://llever.com/rust-cookbook-zh/encoding/complex.zh.html

tar(待补充)

https://rust-lang-nursery.github.io/rust-cookbook/compression/tar.html

科学计算

常量


矩阵

use ndarray::Array;

fn main() {
    let mut a = Array::from_vec(vec![1., 2., 3., 4., 5.]);
    let mut b = Array::from_vec(vec![5., 4., 3., 2., 1.]);
    // let z = a + b;// 最好不要这样写, a 被 borrow 了

    // 改元素值
    a[1] = 10.;

    // 相加
    let w = &a + &b;


    println!("w = {}", w);
    println!("w.sum() = {}", w.sum());
    // println!()

    // a.append()
    // a.push();
    // a.push_row();
    // a.push_column();
    //
    // a.map();

    println!("a = {}", a);

    for elem in a.iter() {
        println!("{}", elem);
    }
}

多维矩阵

use ndarray::arr2;

fn main() {
    let a = arr2(&[[1, 2, 3],
        [4, 5, 6]]);

    let b = arr2(&[[6, 5, 4],
        [3, 2, 1]]);

    println!("矩阵加: {}", &a + &b);
    println!("矩阵加标量: {}", &a + 3);


    let a = arr2(&[[1, 2, 3],
        [4, 5, 6]]);

    let b = arr2(&[[6, 3],
        [5, 2],
        [4, 1]]);

    println!("矩阵积: {}", a.dot(&b));

    println!("标量积: {}", 5 * &a);
}

三角函数

let angle: f64 = 3.14 / 6.0; //弧度
let sin_angle: f64 = angle.sin();

println!("sin: {}, acos = {}, atan = {}", sin_angle, angle.acos(), angle.atan());

// 转度数/弧度
// angle.to_radians()
//  angle.to_degrees()

复数

// num = "*"
let complex_integer = num::complex::Complex::new(10, 20);
let complex_float = num::complex::Complex::new(10.1, 20.1);

println!("Complex integer: {}", complex_integer);
println!("Complex float: {}", complex_float);

jieba

https://github.com/messense/jieba-rs

jieba-rs = "*"

使用

let jieba = Jieba::new();
let sentence = "来到了南京市长江大桥";

// 分词
let words: Vec<&str> = jieba.cut(sentence, false);
assert_eq!(words, vec!["我们", "中", "出", "了", "一个", "叛徒"]);
// 搜索引擎方式分词
let words: Vec<&str> = jieba.cut_for_search(sentence, false);


// 词+词性
let tags = jieba.tag(sentence, false);
for tag in tags {
    println!("{},{}", tag.word, tag.tag);
}

// 词+索引
let tokens: Vec<Token> = jieba.tokenize(sentence, TokenizeMode::Default, false);
// TokenizeMode::Search

添加自定义词典

// 方式1: 读取 .txt 格式的文件作为词典
// dict.txt 内容:
// 创新办 100 nt
// 云计算 100 n

let dict_filename = "dict.txt";

let dict = fs::read_to_string(dict_filename).unwrap();
jieba.load_dict(&mut io::Cursor::new(dict.as_bytes())).unwrap();


// 方式2:在代码里面指定
let dict = "创新办 100 nt\n云计算 100 n";
jieba.load_dict(&mut io::Cursor::new(dict.as_bytes())).unwrap();


// 方式3: 使用 add_word
jieba.add_word("创新办", Some(100), Some("nr"));
jieba.add_word("云计算", Some(100), Some("n"));


// 使用:
let words: Vec<&str> = jieba.cut("李小福是创新办主任也是云计算方面的专家", true);

硬件相关

cpu 数量

// num_cpus = "*"
use num_cpus;

fn main() {
    println!("Number of logical cores is {}", num_cpus::get());
}

测试性能

[dependencies]

bench = "*"

lib.rs

#![feature(test)]
pub mod benchmarks;

benchmarks.rs

#![feature(test)]
extern crate test;

use test::Bencher;


#[bench]
fn bench_pcre2_is_match(b: &mut Bencher) {
    let pcre_re: pcre2::bytes::Regex = pcre2::bytes::Regex::new(r#"he.{0,2}o"#).unwrap();
    b.iter(|| pcre2_is_match(&pcre_re));
}

额外


// 如何匹配 Option
match opt {
  Some(i)=>{},
  None=>{}
}

// 如何匹配 Result
match res {
  Ok{t}=>{}
  Err(e)=>{}
}

改一个数字

fn myfun(a: &mut i32){
    *a+=1;
}
fn main() {
    let mut a=1;
    myfun(&mut a);
    println!("{}",a); // 为 2
}

密码学(待补充)

https://rust-lang-nursery.github.io/rust-cookbook/cryptography/encryption.html

ring::pbkdf2

SQLite 和 Postgres(待补充)

https://rust-lang-nursery.github.io/rust-cookbook/database/sqlite.html

调试/打log(待补充)

https://rust-lang-nursery.github.io/rust-cookbook/development_tools.html

网页编程、爬虫(待补充)

https://llever.com/rust-cookbook-zh/web/mime.zh.html

socket

https://llever.com/rust-cookbook-zh/net/server.zh.html

serde:序列化工具

// serde = { version = "1.0", features = ["derive"] }
// serde_json="*"
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}

let point = Point { x: 1, y: 2 };

// 序列化后的结果 {"x":1,"y":2}
let serialized: String = serde_json::to_string(&point).unwrap();

// 反序列化
let deserialized: Point = serde_json::from_str(&serialized).unwrap();

参考资料

  • https://llever.com/rust-cookbook-zh/
  • rust的12个杀手级库 https://zhuanlan.zhihu.com/p/426329188

您的支持将鼓励我继续创作!