rust中有多种的模块系统来实现程序的结构,包括
包(Packages)。
Crates 。
模块(Modules)
crate 是 Rust 在编译时最小的代码单位。crate 有两种形式:二进制项和库。
二进制项可以被编译为可执行程序,比如一个命令行程序或者一个服务器。它们必须有一个 main
函数来定义当程序被执行的时候所需要做的事情。
库并没有 main
函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西,使其他项目也能使用这些东西。
crate 根则是 Rust 编译器的起始点,在 Cargo 中遵循的一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。同样的,Cargo 知道如果包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。
提供一系列功能的一个或者多个 crate,一个包会包含一个 Cargo.toml 文件,包中可以包含至多一个库 crate,任意多个二进制 crate。
模块是crate的下一级结构,用户可以通过编辑其私有性来改变其他模块可以访问的本模块内容。在 crate 根中,你可以声明一个mod,它有两种声明的方式:
mod a{mod a_1 {fn ta(){}}
}
;
号。然后将其内容放在同名的 .rs 文件中//main.rs
mod a;//a.rs
fn ta(){}
在声明一个模块后,和模块处于同一级的代码块可以直接调用该模块,但是你不能直接调用模块内部的内容,不论是子模块还是函数等内容,因为模块内部的内容默认是私有的,只有模块内部可以直接调用模块内部的内容,为模块内的内容加上pub可以让他能被直接调用
mod a{mod a_1 {fn ta(){}}
}
//如此引用是无效的,因为a_1不是私有的不能访问
crate::a::a_1::ta();mod a{pub mod a_1 {pub fn ta(){}}
}
//如此引用有效的,因为a_1和ta都是pub的可以访问
crate::a::a_1::ta();
在一个模块中,父级不能调用子模块的内容,因为子模块于父模块中其他内容在同一级;但是子模块可以调用父模块的内容
mod a{mod a_1 {fn ta(){//这是有效的a::a_1::tb();}};fn tb(){}//这是无效的a_1::ta();
}
我们可以使用 super 关键字直接定位到父级来避免书写方法所在路径的全程
mod a{fn ta(){//ta的父模块就是cratesuper::tb();};
}
fn tb(){}
如果我们想要使用一个非 pub 模块中的内容,可以使用 use
关键字导入某一个模块,之后我们就可以使用它了,引用时,我们可以使用绝对路径或者相对路径来引用它
crate
开头。self
、super
或当前模块的标识符开头。mod a{mod a_1 {fn ta(){}}
}
//绝对路径
use crate::a::a_1;
//相对路径
use a::a_1;
//可以调用非pub的函数了
ta();
当有多个相同的模块被引入时,我们可以通过 as 关键字为每个模块创建别名来区别每个模块
use std::fmt::Result;
use std::io::Result as IoResult;fn function1() -> Result {
}fn function2() -> IoResult<()> {
}
如果将 pub
和 use
合起来使用,我们不仅将一个名称导入了当前作用域,还允许别人把它导入他们自己的作用域。
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}
pub use crate::front_of_house::hosting;
//外部代码现在可以通过新路径 本文件名称::hosting::add_to_waitlist 来调用 add_to_waitlist 函数
当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行会占用源码很大的空间,我们可以使用嵌套路径将相同的项在一行中引入作用域
use std::{cmp::Ordering, io};
//上面的效果等同于
use std::cmp::Ordering;
use std::io;use std::io::{self, Write};
//上面的等同于
use std::io;
use std::io::Write;
如果希望将一个路径下 所有公有项引入作用域,可以指定路径后跟 *
,glob 运算符:
use std::collections::*;
rust使用 panic!
这个宏来实现异常处理,当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出,通常是检测到一些类型的 bug,而且程序员并不清楚该如何处理它。
fn main() {panic!("crash and burn");
}
//运行结果
$ cargo run
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Result
枚举是用于处理可能发生错误的时候使用的一类枚举,它有OK和ERR两个值,T
代表成功时返回的 Ok
成员中的数据的类型,而 E
代表失败时返回的 Err
成员中的错误的类型
enum Result {Ok(T),Err(E),
}
一个使用它的例子是,打开一个系统文件,因为文件不一定的存在,也不一定可以成功打开,所以rust中打开文件的库函数的返回值是一个Result
use std::fs::File;
fn main() {let f = File::open("hello.txt");let f = match f {Ok(file) => file,Err(error) => panic!("Problem opening the file: {:?}", error),};
}
显然对于刚刚的例子我们不知道错误是找不到文件还是文件损坏等等,所以我们可以通过对于错误类型进行匹配来定位错误,一些错误的类型已经在rust中给我们封装完毕了
let f = match f {Ok(file) => file,Err(error) => match error.kind() {ErrorKind::NotFound => ();other_error => {panic!("Problem opening the file: {:?}", other_error)}},
};
为了简化代码 Result
类型定义了很多辅助方法来处理各种情况
use std::fs::File;fn main() {//这种情况直接输出错误let f = File::open("hello.txt").unwrap();//这种情况可以自定义报错信息let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
//等同于下面的函数
fn main() {let f = File::open("hello.txt");let f = match f {Ok(file) => file,Err(error) => panic!("Problem opening the file: {:?}", error),};
}
另一种处理错误的方式是,让调用者知道这个错误并决定该如何处理,我们可以通过传播错误来获得这个错误从而妥善的处理它
use std::fs::File;
use std::io::{self, Read};fn read_username_from_file() -> Result {let f = File::open("hello.txt");let mut f = match f {Ok(file) => file,Err(e) => return Err(e),};let mut s = String::new();match f.read_to_string(&mut s) {Ok(_) => Ok(s),Err(e) => Err(e),}
}
rust中可以使用 ?
运算符来简化上述的操作
//这个表述和上文代码功能相同
fn read_username_from_file() -> Result {let mut f = File::open("hello.txt")?;let mut s = String::new();f.read_to_string(&mut s)?;Ok(s)
}
上一篇:前端学习路线整理
下一篇:VPP开启调试trace