跳转到内容
Jsonq's Blog
返回

学习 Rust

起因是玩的游戏在解析本地缓存数据以及数据展示上会使用 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



上一篇
一、Java SE