Back
Featured image of post Rust 学习笔记02 - 基本语法

Rust 学习笔记02 - 基本语法

导航页

变量声明

Rust 使用 let 关键字声明变量,和其他新兴语言一样,默认情况下是 Immutable 的。需要使用 mut 关键字使其可变。

fn main() {
  let x = 5;
  println!("The value of x: {}", x);
  let mut y = 5;
  println!("The value of y: {}", y);
}

请注意,这里虽然没有写类型,但不代表 Rust 是动态的。这是类型推断,相当于编译器自动帮你写成了 let x: i32 = 5;

基本类型和 &str(字符串自变量) 可以被声明为编译时常量:

const MAX_POINTS: u32 = 100_000; // compile-time constant

fn main() {
  const STRING: &str = "123123";
  println!("The value of MAX_POINTS: {}", MAX_POINTS);
}

常量必须显式写明类型。同时 top-level 或局部声明都是可以的。

Shadow

虽然不允许修改变量,但 Rust 允许 Shadow 一个变量的名称。比如:

let a = 1;
let a = 100;

然而,我不建议这种做法,很多语言也不建议这种做法。建议读者自行斟酌。

注释

// 单行注释

/*
 * 多行注释
 * /*
 *  * 可以嵌套
 *  */
 */

///
/// 文档注释
///

基本类型

基本类型(primitives),也叫原生类型、原语。是组成所有数据结构的基本元素。

整数类型

长度Signed / 有符号Unsigned / 无符号
8 位i8u8
16 位i16u16
32 位i32u32
64 位i64u64
128 位i128u128
架构特定isizeusize

Rust 的基本类型关键字非常简洁易懂,直接就是类型缩写 + 所占空间,不必纠结 int long long long long int short 的意义。

同时少有的支持 128 位数据类型。

isizeusize 即根据平台使用不同的长度,如 32 位架构上,就占 32 位 ,最常用来存数组下标。

Rust 也支持不同进制的整数字面量:

字面量示例
十进制98_222
十六进制0xFF
八进制0o77
二进制0b1111_000
字节(仅 u8b'A'

可以使用 _ 分隔数字,便于阅读。

浮点类型

Rust 的浮点数遵循 IEEE 754 标准。具有 f32f64 两种长度可选。默认为 f64

浮点数可以省略小数部分,如 0.,但不可省略点号和整数部分。

字符

关键词为 char,字面量使用单引号包围,可存任意 Unicode 定义的字符,占 4 字节。

let a = 'a'
let alpha = 'α'
let emoji = '😄' // 大多数 emoji 都可以存为字符

布尔

关键词为 bool,字面量 truefalse,占 1 字节。

Unit 类型

空元组:()。尽管是元组,但它是一种特殊的类型,而不是复合类型,因为不包含多个值。

复合类型

  • 数组:[1, 2, 3]

    • Rust 的数组索引和多数 C-like 语言一样,从 0 开始
    • 数组的长度是固定的,所以类型注解应写成 let arr: [i32; 5] = [1,2,3,4,5]
    • 多个同样的值可以使用 [0; 5] 这种语法,相当于 [0, 0, 0, 0, 0,]
    • 在编译时,Rust 能够检测部分数组越界。如 let a = [1,2,3]; a[10]; 会直接不过编译。
    • 在运行时,若数组越界了,将会直接 panic 退出程序,而不是继续执行。
  • 元组:(1, true)

    • 类型注解类似这样:(i32, bool)

    • 当元素多于 3 个时,建议使用结构体定义。

    • 可以直接访问对应的类型: x.0 x.1

    • 解构声明:let (x, y, z) = (500, 6.4, 1);

数组和元组的区别在于:数组只能存一种类型;而元组能存多种类型。

字面量后缀

可以使用对应关键字当作类型后缀。例如:

let a = 100i64;
let b = 100i32;
let c = 100i8;

let d = 10.123f32;
let f = 10.112312312f64;

操作符

这部分和大多数类 C 语言一样。就不列出全表了。

值得一提的是,Rust 类似很多新语言(如 Swift),没有 ++ 操作符,避免了各种魔怔写法。建议使用 value += 1 替代。

优先级不明晰的时候,建议括号包裹即可,不用背优先级。

函数

函数使用 fn 声明,函数名使用 snake_case 风格:

fn function() { // 隐式返回 Unit 类型 ()
  println!("Hello Function!");
  another_function();
}

fn another_function() {
    println!("Another function.");
}

参数可以这样写:

fn main() {
    another_function(5, 'g');
}

fn another_function(x: i32, lable: char) {
    println!("The value of x is: {}{}", x, lable);
}

Rust 刻意不支持默认参数,也不支持函数重载。

Rust 允许将不写分号的语句直接返回,而不用写 return

fn return_no_semicolon() -> i32 {
  123123
}

然而我认为这无疑是一种垃圾设计,舍本逐末。为了这里一点的方便,让其他所有地方都要写分号。

况且为何不直接:

fn return_no_semicolon() -> i32 = 123123

控制流

条件

if-else

这里用到了 rand 库,编辑 Cargo.toml

[dependencies]
rand = "0.8.0"
use rand::{thread_rng, Rng}; // 导包

fn main() {
  // 这里的 ..= 是闭区间, 也就是说包括 1 和 100
  // 与之对应的有 .. 是左闭右
  let num = thread_rng().gen_range(1..=100);
  let cmp_num = thread_rng().gen_range(1..=100);
  if num < cmp_num {
    println!("{num} is greater than {cmp_num}!")
  } else {
    println!("{num} is not greater than {cmp_num}!")
  }
}

和其他语言类似,Rust 可以省略包裹条件的括号,但不能省略大括号。

Rust 的条件必须是布尔类型,以下代码会报错:

if 1 {}

if-else-if

// 后面将会省略 main 函数和生成 num 的代码
if num % 3 == 0 {
  println!("{num} is is divisible by 3!")
} else if num % 4 == 0 {
  println!("{num} is is divisible by 4!")
} else if num % 5 == 0 {
  println!("{num} is is divisible by 5!")
} else {
  println!("{num} is not divisible by 3, 4, 5.")
}

match

match num {
  1 => println!("1"),
  2 => println!("2"),
  _ => println!("Other number"),
}

match 要求分支必须是穷尽的,常常用来处理枚举。后面讲到枚举的时候再详细介绍。

作为表达式

穷尽的 ifmatch 允许做看作表达式,用于赋值。

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {}", number);
}

循环

while

let mut count = 0;

while count < 5 {
  count += 1;
  println!("Again! {count}");
}

loop

死循环,类似于 while true {},但对 loop 有着不同的语义。Rust 建议任何能用 while true {} 的地方都用 loop

break & continue

break 用于跳出整个循环。continue 用于跳过本次循环。

fn main() {
  let mut count = 0;

  while count < 5 {
    count += 1;
    print!("{count} ");
    if count == 1 {
      continue;
    }
    if count == 3 {
      break;
    }
  }
}

label

如果存在嵌套循环,可以使用循环标签,跳过指定的循环,而不是最内层循环。

fn main() {
  let mut count = 0;
  'counting_up: loop {
    println!("count = {}", count);
    let mut remaining = 10;

    loop {
      println!("remaining = {}", remaining);
      if remaining == 9 {
        break;
      }
      if count == 2 {
        break 'counting_up;
      }
      remaining -= 1;
    }

    count += 1;
  }
  println!("End count = {}", count);
}

/*
Output:
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
*/

for

for count in 1..=5 {
  println!("Again! {count}");
}

// rev() 用于逆序, 此处会从 5 倒数到 1
for count in (1..=5).rev() {
  println!("Again! {count}");
}

foreach

let a = [10, 20, 30, 40, 50];
for element in a.iter() {
  println!("The value is {element}");
}
comments powered by Disqus