起因是玩的游戏在解析本地缓存数据以及数据展示上会使用 tauri,而对应的解析层逆向用 Rust,用 AI 开发的基础版虽然能用,也能让 AI 继续往上堆功能,但是更希望自己能掌握这门语言的用法。(早都想接触一下 Rust 了,毕竟是编程界的原神)
Table of Contents
Open Table of Contents
基础入门
学习的前提当然是安装 Rust 相关环境,安装 Rust,官网推荐使用 rustup 来运行和管理 Rust 环境,很简单,不赘述。
官方推荐使用 Vscode 进行开发,安装 rust-analyzer 插件,获取自动补全、lint 等功能。
第一个 Rust Demo
fn main(){
println!("Hello World!");
let a: i32 = 5;
println!("a is {}", a); // 这种打印写法和 Java 的 Slf4j 差不多
}
执行 rustc main.rs 编译成可执行文件。然后运行 ./main 即可。
rust-analyzer无法识别到单个 rs 文件,因为默认rust-analyzer只会识别以 cargo 创建的 crate 项目。已有项目可执行cargo init .,新项目可执行cargo new [project-name]
Cargo 跟 Node 的 npm、Java 的 Maven 差不多
数据类型和变量
所有数据类型可查看 Data Types
Unit Type 单元类型
非要列举的话,类似于 Java 中的 void。在 Java 中,一个函数不需要返回值的时候,可以返回 void,rust 则返回()。但语法层面上,void 仅仅只是一个类型,任何值都无法赋给 void,单 unit type () 既是一个类型,同时又是该类型的值。
unit type () 只能赋值给 (),其他任何类型都无法赋给它。
fn test_unit_type() {
let a: () = ();
// let b:() = 5; // mismatched types expected `()`, found integer
println!("a is {:?}", a); // a is ()
}
值得注意的是,unit type 类型的 print 不能直接使用 "a is {}",必须使用 {:?} 或 {#?}。
Integer Type 整数类型
不同于现有大部分语言,Rust 将整数类型分为了有符号整数(i)和无符号整数(u)。整数的二进制都是由 0 和 1 组成的,如果有符号,符号本身需要占一位,因此有符号整数的范围比无符号整数的范围要小。
有符号整数(i)可表示负数和正数,无符号整数(u)只能表示正数。
fn test_integer_type() {
let a: i8 = -127; // √
let b: u8 = 255; // √
println!("a is {}, b is {}", a, b);
// 超出了 i8 的范围
// iteral out of range for `u8` the literal `256` does not fit into the type `u8` whose range is `0..=255`
let d: u8 = 256; // ×
// 无符号整数不能表示负数
// cannot apply unary operator `-` to type `u8` unsigned values cannot be negated
let e: u8 = -1; // ×
}
Float Type 浮点数类型
浮点数只有 f32 和 f64 两种,分别对应 32 位和 64 位。在数字运算中,运算的数据必须是同一种数据类型,否则报错。
当然可以通过数据转换 as 来解决。这种用法和 ts 的 as 非常像,只不过 ts 是类型转换,rust 是数据转换。
fn test_float_type() {
let a: f32 = 255.0;
// cannot subtract `{integer}` from `f32` the trait `Sub<{integer}>` is not implemented for `f32`
let b: u8 = a - 5; // ×
let b: u8 = a as u8 - 5; // √
println!("b is {}", b); // b is 250
}
注意:如果 let a: f32 = 256.0;,在类型转换时,由于 u8 的范围是 0-255,所以会强制变成 255,而不是原来的 266
Boolean Type 布尔类型
boolean 类型只有两个值,true 和 false,可以显示设置类型,也可以让 rust 自动推导类型,rust-analyzer 会自动提示类型。
fn test_boolean_type() {
let a: bool = true; // 显式声明类型
let b = false; // 自动推导类型,但是 rust-analyzer 会自动提示 bool
}
值得注意的是以下例子,Rust 默认声明的变量是 immutable,也就是不可变变量,类似 js 的 const,但是 const 可以给 对象、数组等值的地址赋值(比如 obj.a = 1 arr[0] = 1)。
在 Rust 中,所有变量想要进行更改,都必须添加 mut 修饰符。
fn test_immutable_variable() {
let a: bool = true;
a = false; // cannot assign twice to immutable variable `a`
println!("a is {}", a);
let mut b: u8 = 8;
b = 5; // √
println!("b is {}", b);
}
Character Type 字符类型
char 类型就是 Unicode 字符,且 char 类型必须使用''单引号,双引号会被认为字符串,emoji 也是 char 类型。
fn test_character_type() {
let a: char = 'A';
println!("a is {}", a);
let heart_eyed_cat = '😻';
println!("heart_eyed_cat is {}", heart_eyed_cat);
}
&str 用来表示字符串,字符串是字符的集合,字符串是动态分配的。声明方式有两种,一种使用 &str,一种使用 String。
fn test_string_type() {
let s1: &str = "hello world";
println!("s1 is {}", s1);
let s2: String = String::from("hello");
let s3: String = "world".to_string();
println!("s2 is {}, s3 is {}", s2, s3);
}
Tuple Type 元组类型
TS 中也有 Tuple 的概念 const myTuple: [number, boolean, string] = [5, true, 'Hello'];
Rust 的元组写法是 (类型, 类型, ...),跟 ts 的差不多。
fn test_tuple_type() {
let a: (i32, bool, char) = (5, true, 'A');
println!("a is {:?}", a); // a is (5, true, 'A')
println!("a.0 is {}, a.1 is {}, a.2 is {}", a.0, a.1, a.2); // a.0 is 5, a.1 is true, a.2 is A
}
fn test_tuple_type_destructuring() {
let (a, b, c) = (5, true, 'A');
println!("a is {}, b is {}, c is {}", a, b, c);
}
Array Type 数组类型
数组是静态分配的,长度不可变,且数据类型必须一致。
fn test_array_type() {
let a: [i32; 3] = [1, 2, 3];
println!("a is {:?}", a); // a is [1, 2, 3]
println!("a[0] is {}", a[0]); // a[0] is 1
}
fn test_array_type_destructuring() {
let [a, b, c] = [1, 2, 3];
println!("a is {}, b is {}, c is {}", a, b, c);
}
Slice Type 切片类型
Slice 并不真正拥有指向的数据,只是从指向的类型借用数据,因此不会修改原始数据。正常跟 js 的 slice 效果一样。
但是 Rust 的 Slice 更强,可以指定是否包含上限索引,比如 &a[1..3] 这里取到的是第 1、2 个元素(索引从 0 开始) 两个元素。而 &a[1..=3] 取到的是第 1、2、3 个元素一共三个元素。
let a: [i8; 5] = [1, 2, 3, 4, 5];
let new_a: &[i8] = &a[1..3];
println!("a is {:?}, new_a is {:?}", a, new_a); // a is [1, 2, 3, 4, 5], new_a is [2, 3]
let new_a_1: &[i8] = &a[1..=3];
print!("new_a_1 is {:?}", new_a_1); // new_a_1 is [2, 3, 4]
函数和模块
函数
最基础的函数写法
// #[allow(dead_code)] 让 rust 在编译时忽略未使用的警告
fn get_full_name(first: &str, last: &str) -> String {
// 如果 format 中指定了索引,则按照传入的索引顺序进行填充
// rg: format!("{1} {0}", first, last) // last first
let full_name = format!("{} {}", first, last);
return full_name;
}
fn main() {
let full_name: String = get_full_name("Jackie", "Chan");
println!("full_name is {}", full_name);
}
顺带一提,如果一个函数从未被使用,而又不想让 rust 编译器警告,则可以使用 #[allow(dead_code)] 这种 outer attribute 来明确告诉 rust 忽略这个函数。
模块
rustc 仅仅接收一个参数,也就是说 rust compiler 仅仅能接收一个文件,这意味着无法将多个 rs 文件作为源码一起传递给 rust 进行编译。而所有内容都写在单文件无疑是非常坏的决策,因此可以通过模块,将单文件中的功能拆分为各个模块然后引入。
**注意:**模块文件内的函数,默认都是私有的(private),外部无法使用,如果想要外部使用,则需标记为 pub,调用则通过 模块名::函数名 的方式调用模块内部的函数。
如果 helper.rs 文件和 main.rs 文件同级
// helper.rs
pub fn get_full_name(first: &str, last: &str) -> String {
let full_name = format!("{1} {0}", first, last);
return full_name;
}
// main.rs
mod helper; // pub mod helper; 的 pub 可省略
fn main() {
let full_name: String = helper::get_full_name("Jackie", "Chan");
println!("full_name is {}", full_name);
}
如果 helper.rs 文件在 utils 目录下
#[path = "utils/helper.rs"] // 告诉 rustc helper.rs 文件在 utils 目录下
mod helper;
fn main() {
// ...
}
模块的嵌套子模块
模块内部也可以定义子模块,比如 pub mod utils1{...} pub mod utils2{...} 等等,每个子模块内都可以有很多的方法,在调用侧则 文件名::子模块名::方法名 这种方式调用即可。有点类似于 ts 的命名空间和 Java 的 class 嵌套
// helper.rs
pub mod utils {
pub fn get_full_name(first: &str, last: &str) -> String {
let full_name = format!("{1} {0}", first, last);
return full_name;
}
}
pub mod utils2 {
// ...
}
pub mod utils3 {
// ...
}
// main.rs
fn main() {
let full_name: String = helper::utils::get_full_name("Jackie", "Chan");
}
条件表达式和控制流语句
结合官方文档 Control Flow
if else 语句,Rust 中的 if 是不加括号的
fn test_if() {
println!("Please enter your score: ");
let input_value: &mut String = &mut String::from("");
std::io::stdin().read_line(input_value).unwrap();
// 将接收的字符串转换成数字
// trim() 如果不加这个是无法转换成 u8 的,因为输入默认包含了一个 \n(mac) 或 \r\n(windows)
let score: u8 = input_value.trim().parse::<u8>().unwrap();
if score >= 60 && score < 80 {
println!("Score is passed");
} else if score >= 80 {
println!("Score is excellent");
} else {
println!("Score is failed");
}
}
fn test_if_line() {
let score: u8 = 60;
// 这种写法可以让 if else 语句更简洁,但是复杂的还是推荐用正常的写法
let is_passed: bool = if score >= 60 { true } else { false };
}
while/loop 循环,很简单。loop 只有代码块,不能添加条件,因此只能在 loop 代码块中使用 break 退出循环。
fn test_while() {
let total: u8 = 6;
let mut count: u8 = 0;
while count < total {
println!("count is: {}", count);
count += 1;
// break; 中止循环
}
}
fn test_loop() {
let mut count: u8 = 0;
loop {
println!("count is: {}", count);
if count > 5 {
break;
}
count += 1;
}
}
for 循环,iter() 迭代器,map() 映射,sum() 求和,有点像 Java 的 Lambda 表达式写法
fn test_for() {
let numbers: [i32; 5] = [64, 55, 98, 73, 32];
let mut total: usize = 0;
for value in numbers {
total += value as usize;
}
println!("total is: {}", total);
}
fn test_for_iter() {
let numbers: [i32; 5] = [64, 55, 98, 73, 32];
let total: usize = numbers.iter().map(|&v| v as usize).sum();
println!("total is: {}", total);
}
闭包(匿名函数)
见 Closure