Rust基础学习

Learn Rust - Rust Programming Language (rust-lang.org)

【一起学Rust】Rust介绍与开发环境搭建_rust开发-CSDN博客

包管理工具:cargo

命令行:

cargo new [name]:创建一个新的Rust项目
cargo build:构建项目
cargo run:运行项目
cargo test:运行测试
cargo doc:生成文档
cargo update:更新依赖项
cargo clean:清除构建输出
cargo publish:将软件包发布到crates.io上
cargo install [package]: 安装指定软件包
cargo uninstall [package]: 卸载指定软件包

Rust 全面指南:从基础到高级,一网打尽 Rust 的编程知识_rust语言-CSDN博客

语法

变量声明

1
2
3
4
5
6
7
//定义变量可以使用let关键字,例如:
let x = 10; //会被自动推断为i32类型
let y: i32 = 20;
//上述默认为不可变变量,若要可变变量需要加入mut关键字
let mut z=30;
z=40;
//const关键字用于代表这是一个常亮,不能与mut关键字连用

关键字

关键字
Rust 语言有一组保留的 关键字(keywords),就像大部分语言一样,它们只能由语言本身使用。记住,你不能使用这些关键字作为变量或函数的名称。大部分关键字有特殊的意义,你将在 Rust 程序中使用它们完成各种任务;一些关键字目前没有相应的功能,是为将来可能添加的功能保留的。可以在附录 A 中找到关键字的列表。

目前正在使用的关键字
如下关键字目前有对应其描述的功能。

as - 强制类型转换,消除特定包含项的 trait 的歧义,或者对 use 语句中的项重命名
async - 返回一个 Future 而不是阻塞当前线程
await - 暂停执行直到 Future 的结果就绪
break - 立刻退出循环
const - 定义常量或不变裸指针(constant raw pointer)
continue - 继续进入下一次循环迭代
crate - 在模块路径中,代指 crate root
dyn - 动态分发 trait 对象
else - 作为 if 和 if let 控制流结构的 fallback
enum - 定义一个枚举
extern - 链接一个外部函数或变量
false - 布尔字面值 false
fn - 定义一个函数或 函数指针类型 (function pointer type)
for - 遍历一个迭代器或实现一个 trait 或者指定一个更高级的生命周期
if - 基于条件表达式的结果分支
impl - 实现自有或 trait 功能
in - for 循环语法的一部分
let - 绑定一个变量
loop - 无条件循环
match - 模式匹配
mod - 定义一个模块
move - 使闭包获取其所捕获项的所有权
mut - 表示引用、裸指针或模式绑定的可变性
pub - 表示结构体字段、impl 块或模块的公有可见性
ref - 通过引用绑定
return - 从函数中返回
Self - 定义或实现 trait 的类型的类型别名
self - 表示方法本身或当前模块
static - 表示全局变量或在整个程序执行期间保持其生命周期
struct - 定义一个结构体
super - 表示当前模块的父模块
trait - 定义一个 trait
true - 布尔字面值 true
type - 定义一个类型别名或关联类型
union - 定义一个 union 并且是 union 声明中唯一用到的关键字
unsafe - 表示不安全的代码、函数、trait 或实现
use - 引入外部空间的符号
where - 表示一个约束类型的从句
while - 基于一个表达式的结果判断是否进行循环

作用域

由大括号控制

1
2
3
4
{
let a = 1;
println!("a = {}", a);
}

数据类型

image-20240708124030796.png

元组(tuple)和数组(array)

1
2
3
4
//元组
fn main() {
let tup=(100,'余',true,10.1);
}

元组是以()表示的,里面放入各个元素,且各个元素之间用,分隔

由于rust可以自动推导出其类型,所以这里没有手动为其标注类型:

1
2
3
4
5
6
//元组
fn main() {
let tup:(i32,char,bool,f64)=(100,'余',true,10.1);
println!("{} {} {} {}",tup.0,)
}

这里是手动标注类型

还有数组也很常用,它的使用方法与元组还是有点差距的,其中最大的差别就是,数组中的所有元素的类型必须相同(而上面提到的元组各个元素类型可以不同)

1
2
3
4
fn main() {
let arr=[1,2,3,4,5,6,7,8];
println!("{} {} {} {}",arr[0],arr[1],arr[2],arr[3]);
}

字符串

分为String(可变)和&str(不可变)

1
2
3
4
5
6
7
8
9
10
//String
let mut s = String::new();
s.push_str("hello");
s.push_str(", world!");
//&str
let s = "hello, world!";
//转化
let s = "hello";
let mut s2 = s.to_string();
s2.push_str(", world!");

可变数组Vec

1
2
3
4
5
6

let mut v: Vec<i32> = Vec::new(); // 创建一个空向量(由于是可变的,要使用mut关键字)
v.push(1); // 向向量中添加元素
v.push(2);
v.pop();//向量中弹出元素
//insert,remove

Rust HashTable(Map)

1
2
3
4
5
6
7
8
use std::collections::HashMap;
//insert
let mut scores = HashMap::new();
scores.insert(String::from("Alice"), 100); //这里应该是将字符串字面量转化为String类
scores.insert(String::from("Bob"), 90);
//get
scores.get("Alice");
//有序的map是BTreeMap,要求KEY类型是可以排序的

逻辑判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//if else
if condition {
// code block to execute if the condition is true
} else {
// code block to execute if the condition is false
}
if a>0 {
println!("a>0");
}else if a==0{
println!("a==0");
}else{
println!("a<0");
}
//match
fn main() {
//枚举
enum Direction {
Up,
Down,
Left,
Right,
}
let direction = Direction::Up;
match direction {
Direction::Up => println!("You chose up"), //在使用match语句的时候,对于每一种不同的情况进行匹配,如果匹配成功了之后,就执行这个=>符号后面所对应的那些代码
Direction::Down => println!("You chose down"),
Direction::Left => println!("You chose left"),
Direction::Right => println!("You chose right"),
}
}

循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//while
while condition {
// code block to execute while the condition is true
}
//for
for variable in iterable {
// code block to execute for each value in the iterable
}
for i in 10..20{
println!("{}",i);
}
/*通过语法 10..20就可以自动生成一个10到20的范围对象,
并将该对象中的值,依次赋值给for后面的变量名i,
注意范围运算符 ..生成的范围对象是左闭右开的,
具体来说,10..20 ,i只会等于10到19
但是,10..=20则是10到20
*/
//loop
loop {
// code block to execute repeatedly until break is called
}

image-20240709125242057.png

函数

一个最简单的函数长下面这样:

1
2
3
fn simple(){

}

通过关键字fn加一个函数名字,(){}组成,除此之外什么都没有,同样,它也不能做任何事。

为了能让这个函数干一些事情,我们就需要在函数体,也就是{}中写一些代码:

1
2
3
4
5
fn simple(){
println!("代码");
println!("code");
//.....
}

但只是这样还不够,在没有任何输入的情况下,这个函数能做的事情基本就写死了:无论任何地方调用它,其结果都是一样的(调用随机数除外)。

所以我们就需要函数参数,也就是从函数外部传入的变量,可以让函数内部来使用,参数写在()中。

1
2
3
4
5
6
7
8
9
10
11
12
13
fn simple(i:i32,c:char,f:f64,b:bool){
println!("{} {} {} {}",i,c,f,b);
}
//带有返回值的例子
fn sum(a:i32,b:i32) -> i32{ //->返回值类型
return a+b;
}
fn main() {
let s=sum(100,200);
println!("a+b={}",s);
}


结构

Rust结构体讲解学习,以及impl结构体方法和结构体关联函数_rust struct impl-CSDN博客

Rust 里 struct 语句仅用来定义,不能声明实例,结尾不需要 ; 符号,而且每个字段定义之后用 , 分隔。

结构中也没有方法,要有方法只能用impl去实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Person {
name: String,
age: u32 = 0,
}
//创建实例
let person = Person {
name: String::from("Alice"),
age: 30,
};
//访问字段
person.name
//单元结构体:结构体可以只作为一种象征而无需任何成员:
struct UnitStruct;

结构体方法

方法(Method)和函数(Function)类似,只不过它是用来操作结构体实例的。

如果你学习过一些面向对象的语言,那你一定很清楚函数一般放在类定义里并在函数中用 this 表示所操作的实例。

Rust 语言不是面向对象的,从它所有权机制的创新可以看出这一点。但是面向对象的珍贵思想可以在 Rust 实现。

结构体方法的第一个参数必须是 &self,不需声明类型,因为 self 不是一种风格而是关键字

在调用结构体方法的时候不需要填写 self ,这是出于对使用方便性的考虑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//计算一个矩形的面积:
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}

fn main() {
let rect1 = Rectangle { width: 30, height: 50 }; //创建实例
println!("rect1's area is {}", rect1.area());
}

结构体关联函数

之所以”结构体方法”不叫”结构体函数”是因为”函数”这个名字留给了这种函数:它在 impl 块中却没有 &self 参数。

这种函数不依赖实例,但是使用它需要声明是在哪个 impl 块中的。

一直使用的 String::from 函数就是一个”关联函数”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn create(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}

fn main() {
let rect = Rectangle::create(30, 50); //调用的时候::
println!("{:?}", rect);
}

枚举

【Rust 基础篇】Rust 枚举类型_rust enum-CSDN博客

接口(Trait)

1
2
3
4
5
6
7
8
9
10
11
12
13
trait Fly {
fn fly(&self); //这个方法接收一个self参数,表示对实现这个trait的类型的引用
}
//实例
struct Bird {}

impl Fly for Bird {
fn fly(&self) {
println!("I can fly!");
}
}
let bird = Bird{};
bird.fly();

注解

Rust的注解是以#字符开头的特殊注释,可以为代码提供更多的语义信息或者修改编译器的行为。Rust注解通常放在代码的上方,用于注释某些特定的语法结构或者代码块。下面是一些常用的Rust注解:

  1. #[derive(Debug)]:这个注解用于标记一个结构体或者枚举,让编译器自动生成Debug trait的实现。这样,在调试时,我们可以使用{:?}格式化输出结构体或者枚举的内容,方便快捷。
  2. #[test]:这个注解用于标记一个测试函数,表示它是一个单元测试。测试框架可以通过这个注解自动识别测试函数并执行测试,方便我们编写和运行测试代码。
  3. #[allow(unused_variables)]:这个注解用于关闭编译器的未使用变量警告。如果我们定义了一个变量但是并没有使用它,编译器会发出警告,这个注解可以帮助我们屏蔽这个警告。
  4. #[cfg(target_os = “windows”)]:这个注解用于根据不同的操作系统编译不同的代码。如果我们的代码需要在不同的操作系统上运行,就可以使用这个注解来指定特定的编译条件。
  5. #[no_mangle]:这个注解用于防止Rust编译器对函数名进行重命名,保留原始名称。如果我们的代码需要和其他语言交互,就需要使用这个注解来保证函数名的一致性。

总之,Rust注解可以提供额外的信息,帮助编译器和开发者更好地理解和处理代码,同时也可以修改编译器的行为,以满足特定的需求。

Rust允许开发者自定义注解(Attribute),可以通过宏定义的方式实现。自定义注解可以为代码提供更多的语义信息,也可以修改编译器的行为,方便我们编写高效、优雅的代码。

自定义注解的语法格式如下:

1
2
3
4
5
6
7
8
9
10
#[my_attribute(arg1, arg2, ...)]
fn my_function() {
// code here
}
//例子
#[my_attribute("hello", 42)]
fn my_function() {
// code here
}
定义了一个名为my_attribute的注解,它带有两个参数:一个字符串"hello"和一个整数42。我们可以在函数内部使用这个注解,例如对函数进行标记,表示它是一个特定类型的函数。

模块

Rust的模块系统是基于文件系统的组织方式。每个Rust文件都可以被视为一个模块,模块名与文件名相同。在一个模块中,可以使用mod关键字定义子模块,使用use关键字引用其他模块中的定义。

例如,假设有如下结构的文件系统:

src/
├── main.rs
└── my_module/
├── mod.rs
├── sub_module.rs
└── my_struct.rs

在上述结构中,src目录是Rust项目的根目录,main.rs是项目的入口文件。my_module目录是一个模块,其下有三个文件:

mod.rs:定义了my_module模块的公共接口

sub_module.rs:定义了my_module模块的子模块sub_module。

my_struct.rs:定义了my_module模块的一个结构体MyStruct。

mod.rs中,可以使用mod关键字定义子模块,例如:

1
2
3
4
5
6
7
8
9
10
mod sub_module;

pub struct MyStruct {
pub field: i32,
}

pub fn my_function() {
println!("Hello from my_function!");
}

在上述代码中,定义了一个名为MyStruct的结构体,还有一个名为my_function的函数。同时,使用mod关键字定义了一个名为sub_module的子模块。

sub_module.rs中,可以定义子模块sub_module中的内容,例如:

1
2
3
pub fn sub_function() {
println!("Hello from sub_function!");
}

在上述代码中,定义了一个名为sub_function的函数。

在使用my_module模块中的定义时,需要使用use关键字引用它们,例如:

1
2
3
4
5
6
7
8
use crate::my_module::{MyStruct, my_function};
use crate::my_module::sub_module::sub_function;

fn main() {
let my_struct = MyStruct { field: 42 };
my_function();
sub_function();
}

在上述代码中,使用use关键字引用了MyStruct和my_function,以及sub_function。然后,可以在main函数中使用这些定义。

Rust Cargo.toml

Rust使用 Cargo.toml 文件描述项目的元数据和依赖关系。下面是对 Cargo.toml 文件的详细讲解。

Cargo.toml 文件是一个 TOML(Tom’s Obvious, Minimal Language,即Tom的简洁明了语言)格式的文件,用于描述 Rust 项目的元数据和依赖关系。它通常位于项目的根目录下,与 src/ 目录同级。

下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[package]
name = "myproject"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

[dependencies]
rand = "0.7.0"
serde = "1.0"

[lib]
name = "mylib"
path = "src/mylib.rs"

[[bin]]
name = "mybin"
path = "src/mybin.rs"

[[bin]]
name = "myotherbin"
path = "src/myotherbin.rs"

[features]
default = ["myfeature1"]
myfeature1 = []
myfeature2 = []

[dependencies.mydependency]
version = "1.0"
features = ["myfeature1"]

[workspace]
members = [
"mylib",
"mybin",
"myotherbin",
]

[package] 表示包的元数据,包括包名、版本号、作者和 Rust 版本等信息。[dependencies] 表示包的依赖项,[lib] 表示库的配置,[[bin]] 表示可执行文件的配置,[features] 表示特性的配置,[dependencies.mydependency] 表示依赖的配置,[workspace] 表示工作空间的配置。

引入单个本地模块的方法是,在项目根目录下创建一个名为 src/ 的文件夹,然后在该文件夹下创建 Rust 模块,例如:

1
2
3
4
5
6
// src/mymodule.rs

pub fn myfunction() {
println!("Hello, world!");
}

然后,在 main.rs(或其他程序入口文件)中引入该模块:

1
2
3
4
5
6
7
8
// src/main.rs

mod mymodule;

fn main() {
mymodule::myfunction();
}

引入多个本地模块也是类似的。假设在 src/ 目录下还有一个名为 myothermodule.rs 的 Rust 模块,可以这样写:

1
2
3
4
5
6
7
8
9
// src/main.rs

mod mymodule;
mod myothermodule;

fn main() {
mymodule::myfunction();
myothermodule::myotherfunction();
}

::的用法

Rust中,双冒号有几种用途,主要涉及到模块、类型、枚举变体或trait实现的引用。

  1. 模块和类型的引用::用于引用模块中的类型或函数。例如,String::from("hello")表示调用String类型中的from静态方法,用于创建一个新的字符串。

  2. 枚举变体的引用:当需要引用枚举的某个变体时,也可以使用::。例如,如果有一个枚举Color,其中包含RedGreen两个变体,那么可以通过Color::RedColor::Green来引用这些变体。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    enum Message {
    Hello(String),
    Goodbye
    }

    fn hello(message: Message) {
    match message {
    Message::Hello(s) => println!("Hello, {}", s),
    Message::Goodbye => println!("Goodbye!"),
    }
    }
  3. Trait实现的引用:在Rust中,Trait可以定义一组方法,这些方法可以由实现该Trait的类型提供具体实现。使用::可以明确指定某个类型实现了某个Trait的具体方法。例如,如果有一个Trait Greeter,并且有一个类型Person实现了这个Trait,那么可以通过Person::greet(&person)来调用这个方法,尽管这通常与具体的实例无关,更常见的是使用动态调度(如g.greet()),但在某些情况下,如需要明确类型和方法的关系时,这种语法是有用的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    trait Greeter {
    fn greet(&self);
    }

    struct Person;

    impl Greeter for Person {
    fn greet(&self) {
    println!("Hello from Person!");
    }
    }

    fn use_greeter(g: &dyn Greeter) {
    g.greet();
    }

    fn main() {
    let person = Person;
    use_greeter(&person as &dyn Greeter); // 使用 as 关键字和 &dyn 来创建一个 trait 对象
    }
  4. 在泛型实例化中指定类型参数:当你要创建一个泛型类型的实例并明确指定其类型参数时,可以使用 ::<T>。这在你不想依赖类型推断,或者类型推断不能正确工作时特别有用。

    1
    let vec = Vec::<i32>::new(); // 创建一个包含 i32 类型元素的 Vec 实例
  5. 明确指定作用域:在某些情况下,::也用于明确指定作用域,尤其是在处理与命名空间相关的问题时。这有助于编译器理解你的意图,避免名称冲突。

    例子:

    1
    2
    3
    4
    5
    use std::io;

    fn print_something() {
    io::println!("Hello from io!"); // 使用 std::io 模块中的 println 函数
    }

self和Self

self

self 是一个代表类型实例(或者是类型的引用或者是值)的关键字,在 Rust 的方法中使用 self 可以引用当前类型的实例或者类型本身。

具体来说,当我们定义一个方法时,使用 self 关键字作为方法的第一个参数可以让我们在调用该方法时直接访问类型实例本身

1
2
3
4
5
6
7
8
9
10
11
12
struct Point {
x: f32,
y: f32,
}

impl Point {
fn distance(&self, other: &Point) -> f32 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}

Self

通常在 Rust 的 trait 和 associated function 中使用 Self 来指代实现该 trait 或调用该 associated function 的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Point {
x: f32,
y: f32,
}

impl Point {
//关联函数
fn origin() -> Self {
Point { x: 0.0, y: 0.0 }
}
}

fn main() {
let p = Point::origin();
}

所有权

Rust核心功能之一(所有权)_rust 所有权-CSDN博客

【Rust 基础篇】Rust 所有权详解_rust所有权和内存分析图解-CSDN博客

引用和借用

Rust - 引用和借用_rust 借用和引用-CSDN博客

切片和数组

rust中的数组和切片_rust 数组切片-CSDN博客

常用库

Rust常用库
好的,以下是一些常用的 Rust 库及其常用方法的举例:

  1. std 库
    • println!(): 打印输出信息到控制台
    • Vec: 动态数组类型
    • String: 可变字符串类型
    • HashMap<K, V>: 哈希表类型
  2. serde 库
    • serde_json::to_string(): 将 Rust 结构体序列化为 JSON 字符串
    • serde_json::from_str(): 将 JSON 字符串反序列化为 Rust 结构体
    • serde_yaml::to_string(): 将 Rust 结构体序列化为 YAML 字符串
    • serde_yaml::from_str(): 将 YAML 字符串反序列化为 Rust 结构体
  3. actix 库
    • actix_web::get(): 注册一个 GET 请求处理器
    • actix_web::post(): 注册一个 POST 请求处理器
    • actix_web::web::Json: 解析请求体中的 JSON 数据
  4. tokio 库
    • tokio::net::TcpListener: 创建一个 TCP 监听器
    • tokio::net::TcpStream: 创建一个 TCP 连接
    • tokio::spawn(): 在异步任务池中启动一个新的异步任务
  5. reqwest 库
    • reqwest::get(): 发送一个 GET 请求
    • reqwest::post(): 发送一个 POST 请求
    • reqwest::Client::new(): 创建一个 HTTP 客户端对象
  6. rusoto 库
    • rusoto_s3::S3Client::new(): 创建一个 AWS S3 客户端对象
    • rusoto_ec2::Ec2Client::new(): 创建一个 AWS EC2 客户端对象
    • rusoto_lambda::LambdaClient::new(): 创建一个 AWS Lambda 客户端对象
  7. diesel 库
    • diesel::prelude::*: 导入 Diesel 的预定义类型和函数
    • diesel::insert_into(): 插入一条新的记录
    • diesel::load(): 加载一组记录
  8. log 库
    • log::info(): 记录一条信息级别的日志
    • log::error(): 记录一条错误级别的日志
    • log::warn(): 记录一条警告级别的日志
  9. rand 库
    • rand::thread_rng(): 创建一个随机数生成器对象
    • rand::Rng::gen_range(): 生成一个指定范围内的随机数
    • rand::Rng::shuffle(): 随机打乱一个数组
  10. image 库
    • image::open(): 打开一个图像文件
    • image::save(): 保存一个图像文件
    • image::DynamicImage::resize(): 调整图像尺寸大小