rust语法 3

测试

单元测试

测试单一模块,且能够测试私有接口。

创建 tests 模块并使用 #[cfg(test)] 注解。一般测试模块与目标函数放置于同一个文件。

使用 cargo test 运行测试,cargo test name_slice 可运行指定测试

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
// 私有方法也可测试
fn bad_add(a: i32, b: i32) -> i32 {
    a - b
}

#[cfg(test)]
mod tests {
    // Note this useful idiom: importing names from outer (for mod tests) scope.
    use super::*;

    #[ignore = "这个属性能用于忽略测试"]
    #[test]
    fn test_add() {
        assert_eq!(add(1, 2), 3);
    }

    // 测试函数也可以返回 Result
    #[test]
    fn test_bad_add() -> Result<(), String> {
        if 2 == bad_add(1, 1) {
            Ok(())
        } else {
            Err("the function is bad".to_owned())
        }
    }

    #[test]
    #[should_panic(expected="should panic")] // 当希望发生 panic 时使用
    fn test_should_panic() {
        panic!("should panic")
    }
}

文档测试

文档注解支持commonMarkdown格式,其中的代码块在 cargo test 时也执行。

集成测试

cargo将集成测试放在与src平级的tests目录中,只能使用公开接口。
可以将所有测试都需要的初始设置放入一个函数,再引入test

例如:

// tests/common/mod.rs
pub fn setup() {
    // some setup code, like creating required files/directories, starting
    // servers, etc.
}
// importing common module.
mod common;

#[test]
fn test_add() {
    // using common code.
    common::setup();
    assert_eq!(adder::add(3, 2), 5);
}

开发期依赖

 引入后只在开始时有效

# standard crate data is left out
[dev-dependencies]
pretty_assertions = "1"

Vector

可变长的数组,一个vector对象有3个组成部分:指向数据的指针,长度,容量。

fn main() {
    let mut v1: Vec<i32> = (0..10).collect();
    let mut v2 = vec!["a", "b", "c"];
    println!(
        "{} {} {} {}", // 10 10 3 3
        v1.len(),
        v1.capacity(),
        v2.len(),
        v2.capacity()
    );

    for (_i, x) in v1.iter_mut().enumerate() {
        *x *= 2;
    }

    v2.append(&mut vec!["d", "e", "f", "g"]);
    println!(
        "{} {} {:?}", // 7 7 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
        v2.len(),
        v2.capacity(),
        v1
    );
}

字符串

Rust中有两种字符串:String 和 &str。String是存储在堆上的字节vector,是不以null结尾的utf8字符。&str是指向utf8序列的切片,指向String。

std::str 和 std::string 专门用于处理 str 和 string 类型。

fn main() {
    let s: &'static str = "this is a string data"; // 字面量分配在常量区,只读
    println!("{:p} : {}", s.as_ptr(), s);

    for word in s.split_whitespace().rev() {
        println!("{:p} : {}", word.as_ptr(), word);
    }

    let S = String::from("this is a string data");
    let ss = s.replace("string", "String");
    println!("{:p}", S.as_str());
    println!("{:p}", ss.as_str());
}
// print:
// 0x7ff6dbc656e8 : this is a string data
// 0x7ff6dbc656f9 : data
// 0x7ff6dbc656f2 : string
// 0x7ff6dbc656f0 : a
// 0x7ff6dbc656ed : is
// 0x7ff6dbc656e8 : this
// 0x2699c84f380
// 0x2699c8522c0

智能指针

Box

类似c++的 unique_ptr

智能指针,rust中对象默认分配栈上。可通过 Box 使其分配到堆上,当Box离开作用域时指针和内部对象都销毁。可以通过 * 操作符解引用,直接获得内部对象。

#[derive(Debug, Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}
fn main() {
    let p = Point { x: 1.0, y: 2.0 };
    let bp = Box::new(p);
    println!(
        "{} {} {}", // 16 8 16
        std::mem::size_of_val(&p),
        std::mem::size_of_val(&bp),
        std::mem::size_of_val(&*bp)
    );
}

Rc

Rc(Reference Counting)是对内部对象的引用计数指针,类似c++的 shared_ptr 和 weak_ptr。

内部分为强引用和弱引用,弱引用用于防止循环引用。

fn main() {
    let s = "xxx".to_string();
    let s1 = Rc::new(s);
    println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 0
    {
        let _s2 = Rc::clone(&s1);
        println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 2 0
    }
    {
        println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 0
        let s3 = Rc::downgrade(&s1);
        println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 1
        let _s4 = s3.upgrade();
        println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 2 1
    }
    println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 0
}

Arc

Arc(Atomically Reference Counted)用于多线程间共享所有权。

use std::{sync::Arc, thread};

fn main() {
    let s = Arc::new("string");

    let mut join_vec = vec![];

    for _ in 0..10 {
        let ss = Arc::clone(&s);
        let join = thread::spawn(move || {
            println!("{:?} {:?} {:p}", ss, Arc::strong_count(&ss), ss.as_ptr());
        });
        join_vec.push(join);
    }
    join_vec.into_iter().for_each(|j| {
        let _ = j.join();
    });
}
// "string" 5 0x7ff7f8e29f38
// "string" 6 0x7ff7f8e29f38
// "string" 9 0x7ff7f8e29f38
// "string" 11 0x7ff7f8e29f38
// "string" 10 0x7ff7f8e29f38
// "string" 10 0x7ff7f8e29f38
// "string" 9 0x7ff7f8e29f38
// "string" 9 0x7ff7f8e29f38
// "string" 8 0x7ff7f8e29f38
// "string" 8 0x7ff7f8e29f38

多线程

 一个简单 map-reduce 程序

MapReduce
 use std::{collections::HashMap, thread};

fn main() {
    let data = "86967897737416471853297327050364959
11861322575564723963297542624962850    
70856234701860851907960690014725639
38397966707106094172783238747669219
52380795257888236525459303330302837
58495327135744041048897885734297812
69920216438980873548808413720956532
16278424637452589860345374828574668";
    let mut join_vec = vec![];

    let chunked_data = data.split_whitespace();

    for (i, segment) in chunked_data.enumerate() {
        join_vec.push(thread::spawn(move || -> HashMap<u32, i32> {
            let mut map = HashMap::new();

            segment.chars().for_each(|c| {
                let num = c.to_digit(10).expect("should be digit");

                match map.get_key_value(&num) {
                    Some((&k, &v)) => {map.insert(k, v + 1);}
                    None => {map.insert(num, 1);}
                }
            });
            println!("thread {i} get result: {:?}", map);
            map
        }));
    }
    let mut final_result = HashMap::new();
    join_vec.into_iter().for_each(|res| {
        match res.join() {
            Ok(map) => {
                map.iter().for_each(|(&k, &v)| {
                    match final_result.get(&k) {
                        Some(old_result) => {
                            final_result.insert(k, v + old_result);
                        },
                        None => {
                            final_result.insert(k, v);
                        },
                    }
                });
            },
            Err(err) => {
                println!("thread fail: {:?}", err);
            },
        }
    });
    println!("final result: {:#?}", final_result);
}

标准库的 channel 支持多sender,1个receiver。第三方库 crossbeam_channel 支持多个生产者和消费者。

use std::{sync::mpsc, thread};

static THREAD_NUM: i32 = 3;

fn main() {
    let (sender, receiver) = mpsc::channel();

    let mut join_vec = vec![];

    for i in 0..THREAD_NUM {
        let sender2 = sender.clone();
        let join = thread::spawn(move || {
            sender2.send(i).unwrap();
            println!("thread {i} send: {i}");
        });
        join_vec.push(join);
    }

    for _ in 0..THREAD_NUM {
        receiver.recv().map_or_else(
            |err| println!("err: {err}"),
            |recv| println!("recv: {recv}"),
        );
    }

    for j in join_vec {
        j.join().expect("the child thread panic");
    }
}

IO

 *nix 和 windows 内部使用不同的格式保存字符串,所以Path对应的路径表示不是uft8格式字符串,而是依赖平台的 OsStr。Path 依赖 OsStr 创建,是不可变的。PathBuf 是可变的,与 Path 相关的。关系类似String 和 str。Path 转换为字符串可能失败,但一定可转换为 OsStr。

use std::path::Path;

fn main() {
    let cur_path = Path::new(".");
    let mut new_path = cur_path.join("a").join("b");
    new_path.push("c");
    new_path.push("file.txt");

    println!("{:?} {:?}", cur_path, new_path);
}

文件操作:

use std::{
    fs::{self, File},
    io::{self, Write, BufRead},
    path::Path,
};

fn main() -> io::Result<()> {
    let file_path = Path::new("D:/tmp.txt");
    if file_path.try_exists()? {
        fs::remove_file(file_path)?;
    }

    {
        let mut f = File::create(file_path)?;
        f.write(b"123456\nabcdef")?;
    }

    let file = match File::open(file_path) {
        // 当 File 对象超出作用域时,自动释放资源
        Ok(file) => file,
        Err(err) => panic!("open file failed: {}", err),
    };

    // file.read_buf()
    for ele in io::BufReader::new(file).lines() {
        if let Ok(line) = ele {
            println!("{line}");
        }
    }

    Ok(())
}

 子进程

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

fn main() -> io::Result<()> {
    let output = Command::new("rustc").args(["--version"]).output()?;
    if output.status.success() {
        let s = String::from_utf8_lossy(&output.stdout);
        println!("successded:\n{}", s);
    } else {
        let s = String::from_utf8_lossy(&output.stderr);
        println!("fail:\n{}", s);
    }

    Ok(())
}
posted @ 2023-03-19 19:59  某某人8265  阅读(29)  评论(0编辑  收藏  举报