Appearance
Rust
安装
去到官网,右上角第一个就是Install,跟随指引安装即可。
安装好后可以用命令升级和卸载
- rustup update
- rustup self uninstall
下面这行查看版本号
- rustc --version
本地文档查看
- rustup doc
Cargo
Cargo是Rust的构建系统和包管理工具,用于构建代码、下载依赖库、构建库等。
安装Rust时会安装Cargo。下面的命令查看cargo版本
- cargo --version
下面这行创建新项目
- cargo new 项目名
输入之后会创建文件夹和文件。其中:
cargo.toml,TOML(Tom's Obvious, Minimal Language),是Cargo的配置格式。
里面的内容如下:
[package]
name = "项目名称"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[package]下面主要是包的信息:包名、作者(authors。上面没有,可以自己添加)、版本、Rust版本
[dependencies]下面是依赖包,主要是官方或第三方的依赖。Rust中这些依赖称为crate。
一般来说,源代码文件都放到src文件夹里,项目顶层目录存放README等其他文件。
如果创建的时候没有使用cargo命令,可以把新建src文件夹,将源码放入,并写好Cargo.toml,这样就转化成了cargo管理的项目。
下面这行构建项目,在target/debug
中生成可执行文件。
- cargo build
生成后运行(如果没有构建,这句会先构建再运行)
- cargo run
检查代码
- cargo check
为发布构建,在target/release
中生成可执行文件。优化好,所以编译时间较普通build长。
- cargo build --release
输出到命令行
有两种方式:println!()和print!()
这两种不是函数,而是宏规则。先记住即可,后续会有详细讲解。
rust
let a = 12;
println!("a is {}", a); // 输出 a is 12
// 上面这种输出会在结尾添加换行符,下面这种则不会。
print!("a is {0}, {0} equals a.", a); // 输出 a is 12, 12 equals a.
// 可以用{数字}的方式来决定里面放入哪个变量。例如
let b = 188;
print!("\na is {0}, b is {1}", a, b); // 输出a is 12, b is 188
print!("\na is {}, b is {}", a, b); // 相同。不用编号会依次打印后续的参数。注意花括号数量与变量一致,否则报错
// \n是换行符。还有很多其他转义字符,它们的含义在不同语言当中基本一致
输出到命令行时还有一点要注意,就是它们不支持非字符型的输出,即便你的量值已经是字符型。也就是必须总是以如下方式输出:
rust
println!("{}", a);
注释
rust
// 这是第一种注释方式
/* 这是第二种注释方式 */
/*
* 多行注释
* 多行注释
* 多行注释
*/
/// 用于文档说明的注释
///
/// # Examples
///
/// ```
/// let x = add(1, 2);
///
/// ```
变量与常量
在rust中有三种量值:不可变变量、可变变量、常量。
不可变变量尽管不可变,但实际上允许对它重新赋值
rust
let a = 18;
let a = "ok"; // 允许这样,但是会弹出警告
可变变量就是一般意义上的变量,可以随时改变赋值(但类型需要保持一致,否则报错)
rust
let mut a = 18;
a = 10;
如果想使用同名变量,且改变类型,那么必须每次都使用let
rust
let x = 10;
let x = "10";
print!("{}", x);
// 上述步骤会弹出警告,因为改变变量的类型违背rust的设计初衷,但是编译仍旧是通过的
// 像这种重新给同名变量赋值的方法,叫做重影。
常量用const声明,声明时必须初始化且标注类型。
常量无法绑定到函数的调用结果,或,只能在运行时才能计算出的值。
常量命名必须全部大写,词之间用下划线分开(变量也是用下划线分开)。
数据类型
rust里分成两种类型:标量类型,复合类型。
编译器能够自己推断类型,但如果可能的结果较多,编译器会报错,此时需要显式赋予类型
标量类型,表示单个的值:整数类型、浮点类型、布尔类型、字符类型
值得说的是字符类型。Rust中char类型用来描述语言中最基础的单个字符,字面值使用单引号,占用4字节大小,是Unicode标量值,能够表示拼音、中日韩文、零长度空白字符、emoji表情等。
复合类型:元组(Tuple)、数组
rust
let x: i32 = 10; // 声明时指定数据类型
元组
小括号里,值用逗号分开。每个位置对应一个类型,各元素类型可以不同。
rust
let tup: (i32, f64, u8) = (500, 6.4, 1);
println!("{},{},{}", tup.0, tup.1, tup.2);
// 解构的方式赋值
let (x,y,z) = tup;
println!("{},{},{}", x, y, z);
数组
跟元组类似,但是元素类型相同,长度固定。
想让数据存放在stack上而不是heap上时用数组更好。
rust
let arr = [1,2,3,4,5];
// 声明类型的方式: [类型;长度]
let a:[i32; 5] = [1,2,3,4,5];
// 每个元素都相同时可以这样赋值
let a = [3:5]; // 相当于let a = [3,3,3,3,3];
// 访问时用下标
println!(a[2]);
// 如果越界,编译会通过,运行会报错
如果需要存放不定长的数据,使用Vector(由标准库提供)是更好的选择。
函数
rust
fn main() {
let res = add(1,2);
print!("{}", res);
}
fn add(a: i32,b: i32)->i32{
a+b // 注意这里没有分号结尾,在函数体中,这意味着这一行表达式是这个函数的返回值
// 可以在语句中提前返回,这时需要用return关键字,并且要携带末尾的分号
// return a+b;
}
控制流
- 判断
rust
let a = 59;
if a < 60 {
print!("不及格!");
} else if a > 90{
print!("优秀!");
} else {
print!("良!");
}
- 循环
while和loop
rust
let mut a = 5;
while a > 0 {
println!("while循环");
a = a - 1;
}
rust
let mut a = 5;
loop {
a = a - 1;
println!("loop循环");
if a == 0{
break;
}
}
如果已知范围,使用for循环更加高效
rust
let a = [1,2,3,4,5];
for ele in a{
println!("{}", ele);
}
println!("===============");
// 下面这句也是创建了一个范围。注意,范围是左包又不包,所以右侧的值得是n+1
for ele in (1 .. 6){
println!("{}", ele);
}
所有权
计算机程序必须在运行时管理它们所使用的内存资源。
所有权是 Rust 语言为高效使用内存而设计的语法机制。它能够让 Rust 在编译阶段更有效地分析内存资源的有用性。
所有权有以下三条基础规则:
- Rust 中的每个值都有一个变量,称为其所有者。
- 一次只能有一个所有者。
- 当所有者不在程序运行范围时,该值将被删除。
切片类型
从一段数据中,裁切一段下来。
rust
// 字符串切片
let s = String::from ("helloworld");
let head = &s[0..5];
let tail = &s[5..s.len()];
println!("{}", head);
println!("{}", tail);
println!("{} {}", head, tail)
// ..可以用来表示范围。
// ..y 等价于0..y
// x.. 等价于x到数据结束
// .. 等价于0到数据结束
非字符串切片
rust
let arr = [1,2,3,4,5];
let part = &arr[0..2];
for i in part.iter() {
println!("{}", i);
}
结构体
rust
// 定义
struct Info{
name: String,
gender: u8,
age: u32
}
// struct仅用来定义,结尾不需要分号,字段定义用逗号相隔
// 实例化
let age = 18;
let userinfo = Info{
name: String::from("Tom"),
gender: 1, // 1男, 2女
age // 由于上面有age的声明,所以此处可以简化
};
let userinfo2 = Info{
name: String::from("Jerry"),
..userinfo // 不可以有逗号,至少要改变一个成员
};
元组结构体,可以作为简单的类型定义
rust
struct Color(u8,u8,u8);
let white = Color(255,255,255);
print!("{}", white.0);
结构体在失效时会释放所有字段,所以一般不在结构体中写引用型字段。如果必须在结构体中使用引用型字段,可以通过"生命周期"机制来实现。
想完整输出结构体可以如下书写:
rust
#[derive(Debug)] // 注意Debug的大小写
struct Box{
width: u8,
height: u8,
length: u8
}
let box01 = Box{width: 30, length: 30, height: 30};
println!("{:?}", box01);
结构体方法的实现和使用如下
rust
struct Box{
width: u8,
height: u8,
length: u8
}
impl Box{
fn volume(&self) -> u8 {
self.width * self.height * self.length
}
}
let box01 = Box{width: 2, length: 2, height: 2};
println!("Box Volume is {}", box01.Volume());
多参数的情况:
rust
struct Box{
width: u8,
height: u8,
length: u8
}
impl Box{
fn volume(&self) -> u8 {
self.width * self.height * self.length
}
fn wider(&self, rect: &Box) -> bool {
self.width > rect.width
}
}
let box01 = Box{width: 2, length: 2, height: 2};
let box02 = Box{width: 3, length: 3, height: 3};
println!("box01 wider than box02 is {}", box01.wider(&box02));
结构体函数:在impl块中没有&self参数的
rust
#[derive(Debug)]
struct Box{
width: u8,
height: u8,
length: u8
}
impl Box {
fn create(width: u8, height: u8, length: u8) -> Box {
Box { width, height, length }
}
}
let rect = Box::create(2, 2, 2);
println!("{:?}", rect);
impl可以多写几次,内容会自动拼接
单元结构体:没有成员的结构体
rust
struct Box;
枚举类
rust
#[derive(Debug)]
enum Book {
Papery, Electronic
/*
可以没有属性描述,也可以添加描述
Papery(u32),
Electronic(String),
*/
/*
甚至命名
Papery{index: u32},
*/
}
let book = Book::Papery;
// let book = Book::Papery(1001);
// let book = Book::Papery{index: 1001};
println!("{:?}", book);
match
rust中没有switch,但是可以使用match来达到同样的效果。
rust
enum Book {
Papery {index: u32},
Electronic {url: String},
}
let book = Book::Papery{index: 1001};
let ebook = Book::Electronic{url: String::from("url...")};
match book {
Book::Papery { index } => {
println!("Papery book {}", index);
},
Book::Electronic { url } => {
println!("E-book {}", url);
}
}
更简单的例子
rust
let t = "abc";
match t {
"abc" => println!("yes"), // 像这样,可以直接写成表达式。但是多个条件时,表达式的返回值类型必须相同
_ => {}, // 所有处理分支之外的处理,即默认处理
}
Option枚举类
是Rust标准库中的枚举类,填补Rust不支持null引用的空白。
Rust实际上不允许null存在,但是null有时能很方便地解决问题。
rust
enum Option<T>{
Some(T),
None,
}
// 定义一个可以为空值的类
let opt = Option::Some("Hello");
// 操作前要先判断是否为 Option::None
match opt{
Option::Some(something) => {
println!("{}", something);
},
Option::None => {
println!("opt is nothing");
}
}
初始值为空的 Option 必须明确类型
rust
let opt: Option<&str> = Option::None;
Option 是 Rust 编译器默认引入的,在使用时可以省略 Option:: 直接写 None 或者 Some()
rust
let t = Some(64);
match t {
Some(64) => println!("Yes"),
_ => println!("No"),
}
if let
简短的判断可以用if let完成
rust
// let i = 0;
// match i {
// 0 => println!("zero"),
// _ => {},
// }
// 使用if let 改写
let i = 0;
if let 0 = i {
println!("zero");
}
代码组织
其实就是模块化,将不同的功能写进或编译进不同的文件,从而方便管理和复用。在Rust里用三个概念完成这件事:箱、包、模块
- 箱(Crate)
箱,二进制程序文件或者库文件,以树装解构存在,树根是编译器开始运行时编译的源文件所编译的程序,存于包。
- 包(Package)
当我们使用 Cargo 执行 new 命令创建 Rust 工程时,工程目录下会建立一个 Cargo.toml 文件。工程的实质就是一个包,包必须由一个 Cargo.toml 文件来管理,该文件描述了包的基本信息以及依赖项。
一个包最多包含一个库"箱",可以包含任意数量的二进制"箱",但是至少包含一个"箱"(不管是库还是二进制"箱")。
当使用 cargo new 命令创建完包之后,src 目录下会生成一个 main.rs 源文件,Cargo 默认这个文件为二进制箱的根,编译之后的二进制箱将与包名相同。
- 模块(Module)
相当于Java中类的概念,Rust中使用Module包含并管理某一些函数和属性。
rust
mod nation {
mod government {
fn govern() {}
}
mod congress {
fn legislate() {}
}
mod court {
fn judicial() {}
}
}
这种构建方式最终会形成一棵树,能够在文件系统中访问。在别的系统中,目录结构的分割符可能是斜杠,在Rust中是::
。
像上面这个解构,可以如下路径访问:
crate::nation::government::govern();
这是govern函数的绝对路径。相对路径为:
nation::government::govern();
路径如此,但是实际访问会发现,govern其实是私有(private)的。
另外需要知道的是,每个rust文件实际上就是一个模块
rust
// main.rs
mod second_module;
fn main() {
println!("This is the main module.");
println!("{}", second_module::message());
}
// second_module.rs
pub fn message() -> String {
String::from("This is the 2nd module.")
}
// 注意上面是两个不同的rust文件,一个是main文件,一个是second_module
访问权限
公共(public)和私有(private)。默认为私有。设为公共需要pub关键字。
rust
mod nation{
pub mod government{
pub fn govern(){}
}
mod court{
fn judicial(){
super::congress::legislate(); // 在这里,super指代nation。实际上super指代承载当前mod的那个mod
}
}
}
fn main(){
nation::government::govern();
}
模块中的结构体本身默认是私有的,字段默认也私有。需要pub设置才能变为公有。
rust
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("Rye");
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
}
fn main() {
eat_at_restaurant()
}
枚举本身默认是私有的,需要pub设置为公有。枚举内含字段不具有私有性质,可随意访问。
use关键字
将模块标识符引入当前作用域。说白了,就是为了解决局部模块路径过长的问题
rust
mod nation {
pub mod government {
pub fn govern() {}
}
pub fn govern() {}
}
use crate::nation::government::govern;
use crate::nation::govern as nation_govern; // 重名时可以起个别名
fn main() {
nation_govern();
govern();
}
// 如果不用use,那么main里使用的时候还是要用crate::nation::government::govern这样长串的路径
// 有多少次调用就要写多少次这样的长串,非常麻烦。use就是为了解决这样的麻烦而存在
/* 下面是use和pub一块使用的场景
mod nation {
pub mod government {
pub fn govern() {}
}
pub use government::govern;
}
fn main() {
nation::govern();
}
*/
学会了导入模块,我们可以使用官方库和第三方库进行更便捷的开发了
rust
use std::f64::consts::PI;
fn main() {
println!("{}", (PI / 2.0).sin());
}
错误处理
Rust中分成可恢复错误和不可恢复错误。
可恢复错误,比如文件读写中检测到文件被占用,无法读取或写入。
不可恢复错误,比如妄图向一个不存在的文件中写入内容,或者是访问数组长度之外的位置。
可恢复错误用Result<T,E>类进行处理;不可恢复错误用panic!宏处理。
rust
panic!("Error!");
// panic最终操作是终止程序并交由系统收拾残留内存,还是回溯结构并清理沿途残留数据,可以在Cargo.toml文件中设置
// [profile.release] 意味着在生产环境中
// panic = 'abort' // abort,意味着立即终止程序,由OS打扫
可恢复的错误的用例:
rust
use std::fs::File;
enum Result<T, E> {
Ok(T),
Err(E),
}
fn main() {
let f = File::open("hello.txt");
match f {
Ok(file) => {
println!("File opened successfully.");
},
Err(err) => {
println!("Failed to open the file.");
}
}
}
可以将可恢复错误按不可恢复错误一样处理,使用unwrap或expect。expect可以指定错误消息,unwrap则不行。
rust
use std::fs::File;
fn main() {
let f1 = File::open("hello.txt").unwrap();
let f2 = File::open("hello.txt").expect("Failed to open.");
}
编写错误处理函数
rust
fn f(i: i32) -> Result<i32, bool> {
if i >= 0 { Ok(i) }
else { Err(false) }
}
fn g(i: i32) -> Result<i32, bool> {
let t = f(i)?;
// ?符实际作用是将 Result 类非异常的值直接取出
// ?符仅限于Result<T,E>这种,且E的类型与?处理的Result的E的类型一致
Ok(t) // 因为确定 t 不是 Err, t 在这里已经是 i32 类型
}
fn main() {
let r = g(10000);
if let Ok(v) = r {
println!("Ok: g(10000) = {}", v);
} else {
println!("Err");
}
}
使用kind获取Err类型
rust
use std::io;
use std::io::Read;
use std::fs::File;
fn read_text_from_file(path: &str) -> Result<String, io::Error> {
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
fn main() {
let str_file = read_text_from_file("hello.txt");
match str_file {
Ok(s) => println!("{}", s),
Err(e) => {
match e.kind() {
io::ErrorKind::NotFound => {
println!("No such file");
},
_ => {
println!("Cannot read the file");
}
}
}
}
}
Vector
标准库提供,可存放多个类型相同的值,值在内存中连续存放
rust
let v: Vec<i32> = Vec::new(); // Vec::new()创建空的Vec,但空的Vec无法被编译器推断类型
let mut v2 = vec![1,2,3]; // 有初始值时可以直接使用vec!宏创建。这能够被自动推断出类型
v2.push(4);
println!("{:?}", v2);
下面代码演示了在vector中存放enum
rust
enum SpreadsheetCell{
Int(i32),
Float(f64),
Text(String),
}
fn main(){
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
]
}
String
字符串,是byte的集合,提供一些方法,将byte解析为文本。语言层面上指前面提到的字符串切片。
而String类型,来自标准库,可增长、可修改、可拥有。
rust
// 很多Vec<T>操作都可用于String
let mut s = String::new();
let s1 = "hello world".to_string(); // 转换成String类
let s2 = String::from("Hello World!!");
向已有的字符串中添加
rust
// 添加切片
let mut s = String::from("hello");
s.push_str(" world");
// 附加字符
s.push('!');
s.push('!');
format格式化输出
rust
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = String::from("!!!");
print!("{}", format!("{}{}{}", s1, s2, s3));
HashMap
键值对形式存储数据。
HashMap<K, V>
rust
// hashmap用得较少,所以没有内置,必须引入
use std::collections::HashMap;
// 创建与添加
let mut scores = HashMap::new(); // 创建
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 26);
print!("{:?}", scores.get(&String::from("Blue"))); // Some(10);
/*
实际上hashmap的输出用match更方便
let score = scores.get(&String::from("Blue"));
match score{
Some(s) => println!("{}", s),
None => println!("Nothing to print!");
}
*/
更新hashmap
rust
scores.entry(String::from("Blue")).or_insert(12);
下面是用vector和元组返回出HashMap的例子。仅作为了解
rust
let teams = vec![String::from("Blud"), String::from("Yellow")];
let intial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(intial_scores.iter()).collect(); // 主要留意zip的用法
泛型
rust
// 暂时不用管 std::cmp::PartialOrd 是什么
fn compare<T: std::cmp::PartialOrd>(a:T,b:T) -> T{
if a>b{
a
} else {
b
}
}
fn main() {
let num = compare(2,8);
print!("{}", num);
}
Trait
Trait告诉Rust编辑器某种类型具有哪些并且可以与其他类型共享的功能。它抽象地定义共享行为。
Trait bounds,约束,泛型类型参数指定为实现了特定行为的类型
跟接口interface类似
rust
// 定义
pub trait Summary{
fn summarize(&self) -> String;
}
并发
使用std:🧵:spawn函数创建线程
rust
use std::thread;
use std::time::Duration;
fn spawn_function() {
for i in 0..5 {
println!("spawned thread print {}", i);
thread::sleep(Duration::from_millis(1));
}
}
fn main() {
thread::spawn(spawn_function);
for i in 0..3 {
println!("main thread print {}", i);
thread::sleep(Duration::from_millis(1));
}
}