五、组织管理、文件IO实例、异常处理及泛型

82 阅读7分钟

前言

本文阅读耗时6分钟

目录

五、组织管理、文件IO实例、异常处理及泛型.png

一、组织管理

主要是记录一些概念、文件、模块创建的方式

概念

  • Package(包)
    • Cargo特性,构建、测试共享Crate
  • Crate(单元包)
    • 一个模块树,它可产生一个library或可执行文件
  • Module(模块)
    • use 引入,控制代码组织、作用域、私有路径
  • Path(路径)
    • 为struct、function或者module等命名的方式

Crate

tempsnip.png

  • Crate类型有两种
    • binary
    • library
    • 作用:将相关功能组合到一个作用域中,可以在项目内共享,防止冲突
  • Crate Root
    • 源代码文件,Rust编译器从这里开始,组成Creat的根Module
  • Package
    • 包含一个Cargo.toml,它描述了如何构建Crates,包含它有哪些依赖,版本信息
    • 只能包含0-1个 library crate
    • 可以包含任意数量的binary crate
    • 至少要一个Crate

我们再看图片,其中main.rsbinary cratecrate rootlib.rslibrary create,它们的crate 名字与package名相同,都是crate的根,入口文件。你干脆直接理解为这文件名就是顶级根也是可以的,内部访问时通过crate即可。

Module

定义Module 可以控制作用域和私有性,Module 就是在一个Crate内,将代码分组。
例如我们在lib.rs 中编写

mod front_of_house{
    mod hosting{
        fn add_to_waitlist(){}
        fn sear_at_table(){}
    }
    mod serving{
        fn take_order(){}
        fn server_order(){}
        fn take_payment(){}
    }
}

然后在使用路径在main.rslib.rs中调用

Path及访问权限

//lib.rs
mod front_of_house{
    fn test(){
        super::eat_at_here(); //可以利用super访问同一个Crate下的方法
        crate::eat_at_house();
    }
    pub mod hosting{
        pub fn add_to_waitlist(){} //加了pub 权限,默认都是私有的,rust所有属性默认规则
        fn sear_at_table(){}
    }
    mod serving{
        fn take_order(){}
        fn server_order(){}
        fn take_payment(){}
    }
}
pub fn eat_at_here(){
    crate::front_of_house::hosting::add_to_waitlist();//绝对路径,根create下的create
    front_of_house::hosting::add_to_waitlist();//相对路径
}

如何在 main.rs 中调用呢,因为lib.rsmain.rs 是不同的crate

//main.rs
 fn main() {
    hello_world::eat_at_here(); //项目名称::函数名
 }

use

//Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# 外部包依赖 https://crates.io/
rand = "0.5.5"

导入使用

use rand::Rng;
 fn main() {
}

或者通配符导入使用

use std::{cmp::Ordering, io};//use std::cmp::Ordering; use std::io;
use std::io::{self, Write};// use std::io; use std::io::Write;
use std::io::*;

模块内容移动到其他文件

这个有意思,我们之前都是一股脑将代码写在 lib.rs 文件中,可以通过层级更换写法,更加通俗易懂

1.PNG (lib.rs 就一行代码 mod go_to_school; 就能使用到左侧 go_to_school.rs文件中所有pub代码)

2.PNG (go_to_school.rs 中声明 mod teacher, 对应就需要在 go_to_school文件夹下的school文件夹下创建一个teacher.rs 文件,这个文件名可以随意,这样才能访问到)

3.PNG

二、文件IO实例

我们既然了解到了模块可以拆分,那么就可以利用模块进就行拆分、解耦。
下面主要实例一个读取文件实例

4.PNG lib.rs业务逻辑

use std::fs;
use std::error::Error;

pub fn run(config: Config) -> Result<(), Box<dyn Error>>{
    let contents = fs::read_to_string(config.filename)?;
    println!("read test:\n{}", contents);
    Ok(())
}
pub struct Config {
    query: String,
    filename: String,
}
impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> { //static 为什么?看后面生命周期讲解
        if args.len() < 3{
           //panic!("not enough argumnets!");
           return Err("not enough argumnets!");
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        //Config { query, filename } //args 是借用,不会移交所有权的,构建Config需要字段的所有权
        Ok(Config { query, filename })
    }
}

main.rs 入口调用

use std::env;
use std::process;
use hello_world::Config;
use hello_world as owner;
fn main() {
    let args: Vec<String> = env::args().collect();
    //umwrap 只会返回OK
    let config = Config::new(&args).unwrap_or_else(|err|{
        println!("Problem parsing arguments: {}",err);
        process::exit(1);
    });
    //匹配错误,对错误处理
    if let Err(e) = owner::run(config){
        println!("Application Error: {}", e);
        process::exit(1);
    }
}

三、测试用例

既然我们都把一个文件读的例子写出来了,那是不是得加一个单测?(平时开发Mock测试也算是一项KPI,不得不做吧?不推动用起来,价值真没用,白写,目前我们就是,推动的其他人都没了。。)

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{ //生命周期后面章节记录
    let mut results = Vec::new();
    for line in contents.lines(){
        if line.contains(query){
            results.push(line);
        }
    }
    results
}
#[cfg(test)]
mod tests{
    use super::*;
    #[test]
    fn one_result(){
        let query = "OK";
        let content = "You Want?\nLear Rust??\nOK";
        assert_eq!(vec!["OK"], search(query, content));
    }
}

cargo test 运行(默认所有测试多个线程并行执行,执行线程可以使用 cargo test -- --test-threads = 1)

5.PNG

四、错误异常

Rust 有一套独特的处理异常情况的机制,它并不像其它语言中的 try 机制那样简单。
首先,程序中一般会出现两种错误:可恢复错误和不可恢复错误。
大多数编程语言不区分这两种错误,并用 Exception (异常)类来表示错误。在 Rust 中没有 Exception
对于可恢复错误Result<T, E> 类来处理,对于不可恢复错误使用 panic! 宏来处理。 错误处理是Rust的可靠性特性之一,大部分情况下,在编译的时候就会提示错误,要我们处理。

不可恢复错误

fn main() {  
    panic!("error occured");  
}
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `E:\VSCodeWorkspace\rust\hello_world\target\debug\hello_world.exe`
thread 'main' panicked at 'error occured', src\main.rs:12:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `E:\VSCodeWorkspace\rust\hello_world\target\debug\hello_world.exe` (exit code: 101)

可恢复错误

Rust 中通过 Result<T, E> 枚举类作返回值来进行异常表达

pub enum Result<T, E> {
    /// Contains the success value
    #[lang = "Ok"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    #[lang = "Err"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}

示例

use std::fs::File;
use std::io::ErrorKind;
fn main() {
//1.常规match嵌套
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => {
            println!("File exists!");
            file
        },
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Error create file: {:?}", e),
            },
            other => {
                println!("error in open file");
                panic!("Error in open file {}", error);
            }
        },
    };
    ////2.if let 语句
    // if let Ok(file) = f{
    //     println!("find file");
    // }else{
    //     println!("not find");
    // }
    //3. 更简洁写法
          let f2 = File::open("hello.txt").unwrap_or_else(|error|{
            if error.kind() == ErrorKind::NotFound{
                File::create("hello.txt").unwrap_or_else(|e|{
                    panic!("create error {}", e);
                })
            }else{
                panic!("error {}", error);
            }
      });
}

如何抛一个错误给调用层?

use std::fs::File;
use std::io;
use std::io::Read;
fn read_file() -> Result<String, io::Error>{
    let f= File::open("hello.txt");
    let mut s = String::new();
    f.read_to_string(&mut s)?; //失败会返回Err(e)
    //File::open("hello.txt")?.read_to_string(&mut s)?; 链式调用
    Ok(s)
}

Java中捕获异常,往往很多人都喜欢直接catch Exception,不管具体异常,在Rust中,也可以,只要A错误 from B错误,那么直接捕获B错误即可。或者下面这样也可以,直接捕获所有panic

fn main() {
    let nums:[i32;5] = [1,2,3,4,5];
    panic::catch_unwind(||{
        println!("ERROR!");
        println!("{}", nums[7]);
    });
 }
 输出:
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
Running `E:\VSCodeWorkspace\rust\hello_world\target\debug\hello_world.exe`
ERROR!
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 7', src\main.rs:17:24

日志输出

我们在调试的时候,可能日志会很多,有时候会开启捕获日志,例如 adb logcat > log.log,cargo 也有这个功能,直接 cargo run > log.log 也是可以的。

//同样的指令 cargo run > log.log
println!("Problem parsing arguments: {}",err); //输出到文件
eprintln!("Problem parsing arguments: {}",err);//输出到终端

五、泛型

作用就不多写了,和javac++等一样,提高代码复用能力,在编译时,将占位符替换成具体的类型。直接来个例子吧

fn max(array: &[i32]) -> i32 {  
    let mut max_index = 0;  
    let mut i = 1;  
    while i < array.len() {  
        if array[i] > array[max_index] {  
            max_index = i;  
        }  
        i += 1;  
    }  
    array[max_index]  
}  
  
fn main() {  
    let a = [24631];  
    println!("max = {}"max(&a));  
}

利用泛型可以抽成

fn max<T>(array: &[T]) -> T {  
    let mut max_index = 0;  
    let mut i = 1;  
    while i < array.len() {  
        if array[i] > array[max_index] {  
            max_index = i;  
        }  
        i += 1;  
    }  
    array[max_index]  
}

再来看一个结构体的泛型例子(如何定义、方法泛型、及其使用)

struct Point<T>{
    x:T,
    y:T,
}
impl Point<f64> {
    fn x(&self) -> f64 {
        self.x
    }
}
fn main() {
    let p = Point { x: 1, y: 2 };
    println!("p.x = {}", p.x());
}
//输出
//p.x = 1

如果Point是这样的呢?

struct Point<T,U>{
    x:T,
    y:U,
}
//将一个 Point<T, U> 点的 x 与 Point<V, W> 点的 y 融合成一个类型为 Point<T, W> 的新点。
impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

六、Trait

什么是Trait?
直接理解成接口,例如JavaInterface,只是写法不一样而已

trait Student{
    //默认实现,java 1.8 后,也是有类型的interface接口默认实现
    fn go_to_school(&self){
        println!("every body go to school");
    }
    fn to_string(&self);
}
impl Student for Person {
    fn to_string(&self) {
        println!("name : {} , age : {}" , self.name, self.age);
    }
}
fn main() {
    let p = Person{name:String::from("zhangsan"), age: 18};
    p.go_to_school();
    p.to_string();
}
//输出
//every body go to school
//name : zhangsan , age : 18

Trait作为入参写法

fn printlnSutdent(item: impl Student){
    item.to_string();
}
fn main(){
    let p = Person{name:String::from("zhangsan"), age: 18};
    printlnSutdent(p);
}

Trait配合泛型作为入参写法

//trait bound 语法,单个trait
fn println<T: Student>(item: T){
    item.to_string();
}
//多个triat
fn println2(item: impl Student + Teacher){
    item.to_string();
}
fn println<T: Student + Teacher>(item: T){
    item.to_string();
}

where语句配合Trait定义泛型入参

fn println3<T, U>(item1: T, item2: U) -> ()
where
    T: Student,
    U: Student + Teacher,
{
//....
}

//返回一个trait类型
fn println4<T, U>(item1: T, item2: U) -> impl Student
where
    T: Student,
    U: Student + Teacher,
{
    Person {
        name: String::from("zhangsan"),
        age: 18,
    }
}