Rust 基础语法
1. 变量
Rust 中使用let
关键字定义变量以及初始化
fn main() {
// 定义一个无符号32位整数的变量
// 1.变量名: usize
let a: u32 = 1;
// 2.值加类型
let b = 10086isize;
// 3.自动类型推导
// Rust 编译器会默认推导为uint32类型
let b = 2;
// 自动推断bool
let a = true
}
2. 基本类型
2.1 整型
其中 isize 和 usize 的位数与具体 CPU 架构位数有关。
进制写法
// 十进制写法
let a = 98_222;
// 十六进制写法
let b: usize = 0xff;
// 八进制写法
let c: usize = 0o77;
// 二进制写法
let d: usize = 0b1111_0000;
// 字符的字节表示
let e: u8 = b'A';
2.2 浮点型
浮点数在 Rust 中有两种类型f32
和f64
let a: f32 = 10.0;
let b: f64 = 10.1;
2.3 布尔类型
let a = true;
let b: bool = false;
2.4 字符
Rust 的 char 类型存的是 Unicode 散列值。这意味着它可以表达各种符号,比如中文符号、emoji 符号等。在 Rust 中,char 类型在内存中总是占用 4 个字节大小
fn main() {
let c = 'z';
let z: char = 'Z';
let s = '中';
let s: char = '😀';
println!("{s}")
}
2.5 字符串
String 内部存储的是 Unicode 字符串的 UTF8 编码,而 char 直接存的是 Unicode Scalar Value。
也就是说 String 不是 char 的数组
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
Rust 中的 String 不能通过下标访问
fn main() {
let hello = String::from("Sakura");
let a = hello[0]; // 运行时会报错
}
因为 String 存储的 Unicode 序列的 UTF8 编码,而 UTF8 编码是变长编码。这样即使能访问成功,也只能取出一个字符的 UTF8 编码的第一个字节,它很可能是没有意义的。因此 Rust 直接对 String 禁止了这个索引操作。
字符串转义操作
fn main() {
// 将""号进行转义
let byte_escape = "I'm saying \"Hello\"";
println!("{}", byte_escape);
// 分两行打印
let byte_escape = "I'm saying \n 你好";
println!("{}", byte_escape);
// Windows下的换行符
let byte_escape = "I'm saying \r\n 你好";
println!("{}", byte_escape);
// 打印出 \ 本身
let byte_escape = "I'm saying \\ Ok";
println!("{}", byte_escape);
// 强行在字符串后面加个0,与C语言的字符串一致。
let byte_escape = "I'm saying hello.\0";
println!("{}", byte_escape);
}
禁止转义的字符串字面量
fn main() {
// 字符串字面量前面加r,表示不转义
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
println!("{}", raw_str);
// 这个字面量必须使用r##这种形式,因为我们希望在字符串字面量里面保留""
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果遇到字面量里面有#号的情况,可以在r后面,加任意多的前后配对的#号,
// 只要能帮助Rust编译器识别就行
let longer_delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", longer_delimiter);
}
字节串 ( 类似 golang 中的 bytes 切片 )
很多时候,我们的字符串字面量中用不到 Unicode 字符,只需要 ASCII 字符集。对于这种情况,
Rust 还有一种更紧凑的表示法:字节串。用 b 开头,双引号括起来,比如 b"this is a byte string"
。这时候字符串的类型已不是字符串,而是字节的数组 [u8; N],N 为字节数。
fn main() {
// 字节串的类型是字节的数组,而不是字符串了
let bytestring: &[u8; 21] = b"this is a byte string";
println!("A byte string: {:?}", bytestring);
// 可以看看下面这串打印出什么
let escaped = b"\x52\x75\x73\x74 as bytes";
println!("Some escaped bytes: {:?}", escaped);
// 字节串与原始字面量结合使用
let raw_bytestring = br"\u{211D} is not escaped here";
println!("{:?}", raw_bytestring);
}
2.6 数组
Rust 中数组表示成[T; N]
,由中括号括起来,中间用分号隔开,分号前面表示类型,分号后面表示数组长度。
fn main() {
// 数组的两种定义方式
let a: [isize; 5] = [1, 2, 3, 4, 5];
let a = [1, 2, 3, 4, 5];
}
Rust 中的数组是固定长度的,也就是说编译阶段就能知道它占用的字节数,并且在运行阶段,不能改变它的长度
固定长度数组的好处:
因为固定尺寸的数据类型是可以直接放栈上的,创建和回收都比在堆上动态分配的动态数组性能要好。
是否能在编译期计算出某个数据类型在运行过程中占用内存空间的大小,这个指标很重要
数组常用于开辟一个固定大小的 Buffer(缓冲区),用来接收 IO 输入输出等。也常用已知元素个数的字面量集合来初始化,比如表达一年有 12 个月。
数组的访问
fn main() {
let a: [isize; 5] = [1, 2, 3, 4, 5];
// 下标访问
println!("{:?}",a[1]); // 2
println!("{:?}",a[5]); // 数组索引越界
}
Rust 在编译的时候,就给我们指出了问题,说这个操作在运行的时候会 panic
因为数组的长度是固定的,Rust 在编译的时候分析并且提取了这个数组类型占用空间长度的信息,因此直接阻止了越界访问
2.7 动态数组
Rust 中的动态数组类型是 Vec(Vector),也就是向量,中文翻译成动态数组。它用来存储同一类型的多个值,容量可在程序运行的过程中动态地扩大或缩小,因此叫做动态数组。
fn main() {
let b: Vec<usize> = Vec::new();
let v = vec![1, 2, 3];
// 其中mut表示该变量可以被重新赋值,或其内容可以被修改
let mut v = Vec::new();
v.push(5);
v.push(10);
}
动态数组的访问
fn main() {
let s1 = String::from("superman 1");
let s2 = String::from("superman 2");
let s3 = String::from("superman 3");
let s4 = String::from("superman 4");
let v = vec![s1, s2, s3, s4];
// 这里下标访问越界了
println!("{:?}", v[4]);
}
可以看到,这段代码通过了编译,但是在运行的时候报错了
因为 Vec 是动态数组,是在运行时动态分配内存的,所以需要编译之后运行才能检查错误
2.8 HashMap
Rust 中的哈希表类型为 HashMap。对一个 HashMap 结构来说,Key 要求是同一种类型,比如是字符串就统一用字符串,是数字就统一用数字。Value 也是一样,要求是同一种类型。Key 和 Value 的类型不需要相同。
fn main() {
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
}
3. 复合类型
3.1 元组
元组是一个固定(元素)长度的列表,每个元素类型可以不一样。用小括号括起来,元素之间用逗号隔开。例如:
fn main() {
// 元组的定义
let a: (&str, isize, char) = ("sakura", 1, '2');
// 元组的访问
println!("{:?}", a.0);
println!("{:?}", a.1);
println!("{:?}", a.2);
}
和数组一样, 元组的元素个数也是固定的。
作用:
函数返回值:相当于把多个向返回的值捆绑在一起,一次性返回
当没有任何元素的时候,元组退化成 (),就叫做 unit
类型,是 Rust 中一个非常重要的基础类型和值,unit
型唯一的值实例就是 (),与其类型本身的表示相同。比如一个函数没有返回值的时候,它实际默认返回的是这个unit
值。
3.2 结构体
// 定义结构体
struct User {
active: bool,
username: String,
email: String,
age: u64,
}
fn main() {
// 实例化结构体
let user = User{
active: true,
username: String::from("Sakura")
email: String::from("@gmail.com")
age: 1,
}
}
3.3 枚举
Rust 中使用enum
关键字定义枚举类型。
枚举类型是 Rust 中最强大的复合类型,枚举就像一个载体,可以携带任何类型。
// 定义枚举
enum IpAddrKind {
V4,
V6,
}
fn main() {
//实例化枚举
let version4 = IpAddrKind::V4;
let version5 = IpAddrKind::V6
}
与结构体不同的是:
结构体类型是里面的所有字段(所有类型)同时起作用,来产生一个具体的实例
枚举类型是其中的一个变体起作用,来产生一个具体实例
3.4 举例
定义一个聊天服务的数据结构
#[derive(Debug)]
enum Gender {
Unspecified = 0,
Female = 1,
Male = 2,
}
#[derive(Debug, Copy, Clone)]
struct UserId(u64);
#[derive(Debug, Copy, Clone)]
struct TopicId(u64);
#[derive(Debug)]
struct User {
id: UserId,
name: String,
gender: Gender,
}
#[derive(Debug)]
struct Topic {
id: TopicId,
name: String,
owner: UserId,
}
// 定义聊天室中可能发生的事件
#[derive(Debug)]
enum Event {
Join((UserId, TopicId)),
Leave((UserId, TopicId)),
Message((UserId, TopicId, String)),
}
fn main() {
let alice = User {
id: UserId(1),
name: "Alice".into(),
gender: Gender::Female
};
let bob = User { id: UserId(2),
name: "Bob".into(),
gender: Gender::Male,
};
let topic = Topic { id: TopicId(1), name: "rust".into(), owner: UserId(1) };
let event1 = Event::Join((alice.id, topic.id));
let event2 = Event::Join((bob.id, topic.id));
let event3 = Event::Message((alice.id, topic.id, "Hello world!".into()));
println!("event1: {:?}, event2: {:?}, event3: {:?}", event1, event2, event3);
}
Gender
:一个枚举类型,在 Rust 下,使用 enum 可以定义类似 C 的枚举类型UserId/TopicId
:struct 的特殊形式,称为元组结构体。它的域都是匿名的,可以用索引
访问,适用于简单的结构体。User/Topic
:标准的结构体,可以把任何类型组合在结构体里使用。Event
:标准的标签联合体,它定义了三种事件:Join、Leave、Message。每种事件都有
自己的数据结构。
4. 流程控制
4.1 判断语句
fn main() {
let x = 1;
// 在这里,if else 返回了值
let y = if x == 0 {
// 代码块结尾最后一句不加分号,表示把值返回回去
100
} else {
// 代码块结尾最后一句不加分号,表示把值返回回去
101
};
println!("y is {}", y);
}
4.2. 循环语句
Rust 中有三种循环语句,分别是 loop、while、for。
loop
loop 用于无条件,无限循环
fn main() {
let mut counter = 0;
// 这里,接收从循环体中返回的值,对result进行初始化
let result = loop {
counter += 1;
if counter == 10 {
// 使用break跳出循环,同时带一个返回值回去
break counter * 2;
}
};
println!("The result is {result}");
}
while
fn main() {
let mut number = 3;
// 条件为真,执行循环体
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
for
for 循环在 Rust 中,基本上只用于迭代器(暂时可以想象成对数组,动态数组等)的遍历。
fn main() {
let a = [10, 20, 30, 40, 50];
// 使用for循环遍历数组
for element in a {
println!("the value is: {element}");
}
}
..
生成遍历区间
fn main() {
// 左闭右开区间
for number in 1..4 {
println!("{number}");
}
println!("--");
// 左闭右闭区间
for number in 1..=4 {
println!("{number}");
}
println!("--");
// 反向
for number in (1..4).rev() {
println!("{number}");
}
println!("--");
// 反向左闭右闭
for number in (1..=4).rev() {
println!("{number}");
}
}
打印 26 个字母
fn main() {
for ch in 'a'..='z' {
println!("{ch}");
}
}
4.3. 举例
通过三种循环实现斐波那契数列
fn fib_loop(n: u8) {
let mut a = 1;
let mut b = 1;
let mut i = 2u8;
loop {
let c = a + b;
a = b;
b = c;
i += 1;
println!("next val is {}", b);
if i >= n {
break;
}
}
}
fn fib_while(n: u8) {
let (mut a, mut b, mut i) = (1, 1, 2);
while i < n {
let c = a + b;
a = b;
b = c;
i += 1;
println!("next val is {}", b);
}
}
fn fib_for(n: u8) {
let (mut a, mut b) = (1, 1);
for _i in 2..n {
let c = a + b;
a = b;
b = c;
println!("next val is {}", b);
}
}
fn main() {
let n = 10;
fib_loop(n);
fib_while(n);
fib_for(n);
}
5. 函数和模块
在 Rust 中:
访问结构体的成员函数或者变量使用点
.
运算符访问命名空间(namespace)或者对象的静态函数使用双冒号
::
运算符
5.1. 函数
// 定义函数,a和b代表形参
fn print_a_b(a: i32, b: char) {
println!("The value of a b is: {a}{b}");
}
fn main() {
// 调用函数
print_a_b(5, 'h');
}
5.2. 闭包
fn main() {
// 标准的函数定义
fn add_one_v1 (x: u32) -> u32 { x + 1 }
// 闭包的定义1
let add_one_v2 = |x: u32| -> u32 { x + 1 };
// 闭包的定义2,省略了类型标注
let add_one_v3 = |x| { x + 1 };
// 闭包的定义3,花括号也省略了
let add_one_v4 = |x| x + 1;
let add_one = |x| x + 1;
let a_vec: Vec<u32> = vec![1,2,3,4,5];
let vec2: Vec<_> = a_vec.iter().map(add_one).collect();
}
// 捕获函数中的局部变量
fn main() {
let a = 10u32; // 局部变量
let add_v2 = |x: u32| -> u32 { x + a }; // 定义一个闭包
// let result1 = add_v1(20); // 调用函数
let result2 = add_v2(20); // 调用闭包
println!("{}", result2);
}
6. 模块(导包)
service
目录表示一个模块
chat.rs
表示改模块下的一个子模块
service.rs
表示service
这个模块的入口
RustStudy
├── Cargo.lock
├── Cargo.toml
└── src
├── service // 子目录
│ └── chat.rs
├── service.rs // 与子目录同名的.rs文件,表示这个模块的入口
└── main.rs
// 在service.rs 中导入chat.rs子模块
mod chat;
// 在main.rs中,导入service模块
mod service;
这样就完成了模块的导入
7. 测试
8. Html 转 Markdown的代码
fn main() {
let url = "https://www.rust-lang.org/";
let output = "rust.md";
// 打印url
println!("Fetching url: {}", url);
// 1. 使用 reqwest 库的 blocking API 发起 HTTP GET 请求
// 2. `reqwest::blocking::get(url)` 返回一个 `Result<Response, Error>` 类型的值
// 3. 使用 `.unwrap()` 处理结果,若请求成功则提取出 `Response` 对象,否则触发 panic
// 4. 调用 `Response` 对象的 `text()` 方法获取响应体的文本内容,返回一个 `Result<String, Error>`
// 5. 再次使用 `.unwrap()` 处理结果,若获取文本成功则得到一个 `String`,否则触发 panic
let body = reqwest::blocking::get(url).unwrap().text().unwrap();
println!("Converting html to markdown...");
// 使用 html2md 库的 `parse_html` 函数将 HTML 字符串转换为 Markdown 字符串
let md = html2md::parse_html(&body);
// 使用 Rust 标准库中的 `fs` 模块,调用 `write` 函数将 Markdown 文本写入指定文件
// 1. `output` 变量存储目标文件名(本例中为 "rust.md")
// 2. `md.as_bytes()` 将 Markdown 字符串转换为其字节表示(`Vec<u8>`)
// 3. `fs::write(output, md.as_bytes())` 返回一个 `Result<(), std::io::Error>` 类型的值
// 4. 最后使用 `.unwrap()` 处理结果,若写入文件成功则不返回任何值,否则触发 panic
fs::write(output, md.as_bytes()).unwrap();
println!("Converted markdown has been saved in {}.", output);
}
9. 模式匹配
fn process_event(event: &Event) {
// 使用match表达式对传入的`event`进行模式匹配
match event {
// 如果`event`等于`Event::Join((uid, _tid))`这种形式...
Event::Join((uid, _tid)) => {
// ...则执行以下代码块:
println!("user {:?} joined", uid);
// 打印一条消息,表示用户`uid`加入了。`uid`的值由匹配表达式中捕获的变量`uid`提供。
// `_tid`是未使用的变量名,以下划线表示,表明这里虽然捕获了元组中的第二个元素(可能是一个线程ID或其他标识符),但并未在后续代码中使用。
},
// 如果`event`等于`Event::Leave((uid, tid))`这种形式...
Event::Leave((uid, tid)) => {
// ...则执行以下代码块:
println!("user {:?} left {:?}", uid, tid);
// 打印一条消息,表示用户`uid`离开了,并附带离开了某个与`tid`关联的上下文(可能是线程、会话等)。`uid`和`tid`的值分别由匹配表达式中捕获的相应变量提供。
},
// 如果`event`等于`Event::Message((_, _, msg))`这种形式...
Event::Message((_, _, msg)) => {
// ...则执行以下代码块:
println!("broadcast: {}", msg);
// 打印一条消息,表示接收到并广播了一条消息内容为`msg`的消息。这里前两个下划线(`_`)表示忽略元组中的前两个元素(可能是发送者信息和接收者信息),仅关心第三个元素`msg`,即消息文本。
},
}
}
评论区