前言
本文阅读耗时6分钟
目录
一、组织管理
主要是记录一些概念、文件、模块创建的方式
概念
- Package(包)
- Cargo特性,构建、测试共享Crate
- Crate(单元包)
- 一个模块树,它可产生一个library或可执行文件
- Module(模块)
- use 引入,控制代码组织、作用域、私有路径
- Path(路径)
- 为struct、function或者module等命名的方式
Crate
- Crate类型有两种
- binary
- library
- 作用:将相关功能组合到一个作用域中,可以在项目内共享,防止冲突
- binary
- Crate Root
- 源代码文件,Rust编译器从这里开始,组成Creat的根Module
- 源代码文件,Rust编译器从这里开始,组成Creat的根Module
- Package
- 包含一个Cargo.toml,它描述了如何构建Crates,包含它有哪些依赖,版本信息
- 只能包含0-1个 library crate
- 可以包含任意数量的binary crate
- 至少要一个Crate
我们再看图片,其中main.rs
是binary crate
的crate root
, lib.rs
是library 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.rs
或 lib.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.rs
和 main.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
文件中,可以通过层级更换写法,更加通俗易懂
(lib.rs
就一行代码 mod go_to_school;
就能使用到左侧 go_to_school.rs
文件中所有pub
代码)
(go_to_school.rs
中声明 mod teacher
, 对应就需要在 go_to_school
文件夹下的school
文件夹下创建一个teacher.rs
文件,这个文件名可以随意,这样才能访问到)
二、文件IO实例
我们既然了解到了模块可以拆分,那么就可以利用模块进就行拆分、解耦。
下面主要实例一个读取文件实例
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
)
四、错误异常
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);//输出到终端
五、泛型
作用就不多写了,和java
、c++
等一样,提高代码复用能力,在编译时,将占位符替换成具体的类型。直接来个例子吧
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 = [2, 4, 6, 3, 1];
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?
直接理解成接口,例如Java
的Interface
,只是写法不一样而已
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,
}
}