Rust 学习-基础语法篇

474 阅读8分钟
  • rustup:是 Rust 的安装程序,管理 Rust 版本和相关工具的命令行工具
    • 更新 rust:rustup update
    • 卸载 rust:rustup self uninstall
  • rustc:是 Rust 编程语言的编译器
    • 检查是否安装了 rust:rustc --version
  • cargo:是 Rust 的构建系统和包管理器
    • 检查是否安装了 cargo:cargo --version
    • 使用 cargo 创建项目:cargo new hello_world
    • cargo build: 构建项目
    • cargo run :构建并运行项目
    • cargo check :在不生成二进制文件的情况下构建项目来检查错误
    • cargo build --release 优化编译项目
    • cargo update # 更新所有依赖
    • cargo update -p regex # 只更新 “regex”

创建项目

我们使用 cargo 来创建一个 hello_world 项目

$ cargo new hello_world
$ cd hello_world

可以看到项目的结构和配置文件都是由 cargo 生成:

$ tree
.
├── .git
├── .gitignore
├── Cargo.toml
└── src
    └── main.rs

Cargo.toml,是 Cargo 使用的配置文件,其中包含了项目的名称、项目的版本、使用的 Rust 版本以及项目依赖:

image.png

代码内容很简单,就是打印一句 “Hello, world!” , ./src/main.rs:

image.png

  1. 手动编译和运行项目

image.png

  1. cargo run,直接运行项目

在 hello_world 目录下运行 cargo run, 此命令对项目进行编译,然后再运行,等同于第一种方法中的两个指令。

image.png

语法总结

变量

  1. 变量绑定

let a = "hello world",rust 中给变量 a 赋值的这种方式叫作“变量绑定”,因为 rust 中有个“所有权” 的概念。

  1. 变量可变性

rust 中的变量,默认是不可变的,可以通过 mut 关键字来实现变量的可变性。例如:

fn main() {
    let x = 1;
    println!("x 的值是: {}",x);
    x = 2;
    println!("x 的值是: {}",x);
}

以上再次给 x 绑定值时会发生错误,因为它是不可变的,正确的应该是:

fn main() {
    let mut x = 1;
    println!("x 的值是: {}",x);  // x 的值是: 1
    x = 2;
    println!("x 的值是: {}",x);  // x 的值是: 2
}
  1. 常量

常量的值也是不可修改,但是和变量有些差异:

  • 常量不使用 mut
  • 常量使用 const 关键字来声明
  • 常量值的类型必须标注
  1. 变量遮蔽(shadowing)

在 rust 中,我们可以使用 let 多次声明一个相同的变量名,后声明的会遮蔽之前所声明的。例如:

let x = 1;
let x = x +1;
println!("x 的值是: {}",x); // x 的值是: 2

遮蔽与使用 mut 的区别是:

  • 如果忘记使用 let 来再次声明,则会导致编译时错误。
  • 遮蔽其实是创建了一个同名的新变量,这个变量的类型可以与之前不同。
  1. 未使用变量

rust 中,如果创建了一个变量,但是没有使用到它,那么会产生一个警告,我们可以使用下划线作为变量名的开头,来消除警告:

let _x = 1;
  1. 变量解构

let 表达式还可以进行变量的解构,例如:

// a = true,不可变; b = false,可变 
let (a, mut b): (bool,bool) = (true, false); 
println!("a = {:?}, b = {:?}", a, b); //a = true, b = false

b = true;
println!("b = {:?}", b); //b = true

解构式赋值

在 Rust 1.59 版本后,可以在赋值语句的左式中使用元组、切片和结构体模式了。

struct Struct {
    e: i32
}

fn main() {
    let (a, b, c, d, e);

    (a, b) = (1, 2);
    // _ 代表匹配一个值,但是我们不关心具体的值是什么,因此没有使用一个变量名而是使用了 _
    [c, .., d, _] = [1, 2, 3, 4, 5];
    Struct { e, .. } = Struct { e: 5 };

    assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);
}

这里 [c, .., d, _] 是一个模式,表示要匹配的数组的结构。其中 c 是数组的第一个元素,.. 表示中间的所有元素(这些元素我们不关心,所以用 .. 来表示),d 是数组的倒数第二个元素,_ 是数组的最后一个元素。
这种解构赋值的方式会将数组的元素分别赋值给 cd 和 _。所以在这个例子中,c 的值会是 1d 的值会是 4,而 _ 的值会是 5

数据类型

rust 是 静态类型 语言,每一个值都有自己的数据类型。

数值类型

  1. 整型:
长度有符号类型无符号类型
8 位i8u8
16 位i16u16
32 位i32u32
64 位i64u64
128 位i128u128
视架构而定isizeusize
  • isize 和 usize 类型取决于程序运行的计算机 CPU 类型: 若 CPU 是 32 位的,则这两个类型是 32 位的,同理,若 CPU 是 64 位,则是 64 位。
  • rust 整型默认使用 i32
  1. 浮点型

rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,所有的浮点型都是有符号的。

  1. 序列(Range)

序列用来生成连续的数值,例如 1..5,生成从 1 到 4 的连续数字,不包含 5 ;1..=5,生成从 1 到 5 的连续数字,包含 5。序列只允许用于数字或字符类型,它的用途很简单,常常用于循环中:

for i in 'a'..='z' {
    println!("{}",i); 
}

字符类型(char)

  • rust 的字符不仅仅是 ASCII,所有的 Unicode 值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等。
  • 由于 Unicode 都是 4 个字节编码,因此字符类型也是占用 4 个字节。
  • rust 的字符用 '' 来表示, "" 来表示字符串。

布尔

  • rust 中的布尔类型有两个可能的值:true 和 false
  • 布尔值占用内存的大小为 1 个字节。

单元类型

  • 单元类型就是 (),例如 fn main() 这个 main 函数返回的就是单元类型 ()
  • 可以用 () 作为 map 的值,表示不关注具体的值,只关注 key。 这种用法和 Go 语言的 struct{}类似,可以作为一个值用来占位,但是完全不占用任何内存。

函数

  1. 函数声明
  • rust 中使用 fn 关键字来声明函数。
  • 函数和变量名采用 snake case 规范,所有字幕都是小写且使用下划线分割单词。
  • 每个函数参数都需要标注类型。

例如:

fn add(i: i32, j: i32) -> i32 {
    i + j
}

上面代码中没有使用 return 关键字来返回值。其中的 i + j 没有以分号结尾,是一条表达式,求值后,返回一个值。在 rust 中,函数的返回值等同于函数体最后一个表达式的值。

  1. 语句和表达式
  • 语句:执行一些操作但不返回值的指令。 
  • 表达式:计算并产生一个值。

数调用是一个表达式。宏调用是一个表达式。用大括号创建的一个新的块作用域也是一个表达式,例如:

fn main() {
    let y = {
        let x = 1;
        x + 2
    };

    println!("y 的值为: {y}");  // y 的值为: 3
}

x + 2 这一行没有分号,最后的值返回给 y。表达式的结尾没有分号,如有加上了分号就变成了语句。语句不会返回值。

  1. 无返回值()

单元类型 (),是一个零长度的元组。可以用来表达一个函数没有返回值:

  • 函数没有返回值,那么返回一个 ()
  • 通过 ; 结尾的表达式返回一个 ()
  1. 发散函数 !

发散函数(diverging function)通常用来表示永远不会返回的函数。发散函数的返回类型是 !,它不是任何其他类型的子类型,因此无法从发散函数返回任何值。

发散函数的使用场景:

  • 无限循环的函数
fn game_loop() -> ! {
    loop {
        // ...
    }
}
  • 彻底退出程序
fn exit_program() -> ! {
    std::process::exit(0);
}
  • 当出现不可恢复的错误时
fn unrecoverable_error() -> ! {
    panic!("Unrecoverable error occurred");
}

控制流

if 表达式

  1. if...else if...else...
let number = 7;

if number < 5 {
    println!("Number is less than 5");
} else if number == 5 {
    println!("Number is equal to 5");
} else {
    println!("Number is greater than 5");
}
  • if 语句块是表达式,条件 必须 是 bool 值
  1. 在 let 语句中使用 if:
let condition = true;
let number = if condition {
    1
} else {
    2
};
  • 用 if 来赋值时,要保证每个分支返回的类型一样,如果返回类型不一致就会报错

循环

  1. loop

loop 就是一个简单的无限循环,可以在内部实现逻辑通过 break 关键字来控制循环何时结束。

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;
        if counter == 5 {
            break counter * 2; 
        }
    };

    println!("The result is {}", result); // The result is 10
}
  • break 可以单独使用,也可以带一个返回值,有些类似 return
  • loop 是一个表达式,因此可以返回一个值。
  1. while

while 需要一个条件来循环,当该条件为 true 时,继续循环,条件为 false,跳出循环,这种结构消除了很多使用 loopifelse 和 break 时所必须的嵌套,这样更加清晰。

fn main() {
    let mut counter = 0;

    while counter != 5  {
        counter = counter + 1;
    }

    println!("The counter is {}", counter); // The counter is 5
}
  1. for

可以使用 for 循环来访问一个集合中的每个元素:

fn main() {
    let a = [1, 2, 3];

    for v in a {
        println!("the v is: {v}");
    }
}

//the v is: 1
//the v is: 2
//the v is: 3