三、Rust 语言基础

148 阅读5分钟

  变量和可变性

变量是用来存储数据的标识符,在程序执行过程中其对应的值是可以被改变的。在 Rust 中,对于变量的命名使用 snake_case 的形式。变量分为可变变量不可变变量, 默认情况下声明的变量是不可变的。

通过控制变量的可以变性,来避免程序开发中的数据竞争和其他并发问题。在变量声明的时候,需要显性的考虑清楚变量的可变性。使用不可变变量可保证程序的安全性,但是会丧失一定的灵活性。

fn main() {
    // 默认声明不可变变量
    let x = 9;
    println!("{}", x);
    // x = 10; cannot assign twice to immutable variable `x`
    println!("{}", x);

    // 可变变量
    let mut y = 9;
    y = 10;
    println!("{}", y);

    //变量遮蔽; 复用变量名 y, 此时的 y 已经和上面的 y 没有任何关系; 可以避免起新的变量名
    let y = "zjl";
    println!("{}", y);

    // 使用 _ 开头,声明一个不使用的变量,常见用项目设计阶段
    let _y = 9;

    // 变量解构
    let (a, mut b): (bool, bool) = (true, false);
    println!("{}:{}", a, b)
}

  常量

常量一般用于声明程序中一些固定值,通常伴随整个生命周期且不会更改,命名方式使用 SNAKE_CASE 的形式。相较于不可变变量,常量使用 const 进行声明,还需要声明具体的类型。

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

fn main() {
    println!("{}", THREE_HOURS_IN_SECONDS);
}

  基本数据类型

Rust 是静态类型的语言,所有的值在编译时期就需要知道其具体的数据类型。当然,有些时候开发者无需指定具体类型,编译器会进行类型推断出具体类型。在 Rust 中可以将数据分为标量类型和复合类型。标量(scalar) 类型表示单个值。在 Rust 中有四个基本的标量类型:整型、浮点型、布尔和字符。

整型:对于 isizeusize 类型,其长度由程序运行时的计算机体系结构确定。使用 64 位架构系统则为 64 位,若使用 i32 位架构系统则为 32 位。

浮点型:是带有小数点的数字,在 Rust 中浮点类型(简称浮点型)数字也有两种基本类型。Rust 的浮点型是 f32f64,它们的大小分别为 32 位和 64 位。默认浮点类型是 f64,因为在现代的 CPU 中它的速度与 f32 的几乎相同,但精度更高。所有浮点型都是有符号的。

布尔类型: Rust 中的布尔类型也有两个可能的值:truefalse。布尔值的大小为 1 个字节。

字符类型: Rust 的 char(字符)类型是该语言最基本的字符类型。

fn main() {
    let c = '😊';
    println!("{}", std::mem::size_of_val(&c)); // 4 byte
    let (a, b) = (1, 1.0);
    println!("{}: {}", a + 1, b + 1.0);
}

  函数

函数是一段具有特定功能的代码块,它可以接受输入(称为参数或变量),执行特定的操作,然后可能返回一个结果。函数的主要目的是封装一些代码,使其可以被重复使用,提高代码的可读性和可维护性。 在 Rust 中,函数命名使用 snake_case。

image.png

函数由一些列的语句组成,也可以选择表达式结尾,作为函数的返回结果。

fn main() {
    // 语句:执行一些操作但是不返回值
    let (a, b) = (5, 6);

    let b = {
        let y = a + b;
        y // y 是一个表达式,即返回具体的值; 后面加 ';' 就是一个语句
    };
    println!("{}", b)
}

// 函数永不返回
fn f() -> ! {
    loop {
        println!("{}", 0);
    }
}

fn add(a: i32, b: i32) -> i32 {
    return a + b;
}

fn add_v1(a: i32, b: i32) -> i32 {
    a + b //直接使用表达式, 无需使用语句:return a+b
}

  注释

有人认为好的代码不需要注释,代码是可以自解释的。我并不完全认同这个观点,毕竟代码是写给计算机用来执行的,不像自然语言一样可以表达含义。通过注释可以直接表述复杂的代码逻辑。

在 Rust 中,通常使用双斜杠开头,直到行尾结束。对于超出单行的注释,需要在每行的行首增加//。此外,还有一种 /// 的注释,即文档注释,会生成对应的 html 文档,同时还支持嵌入代码,使用 cargo test 会被执行。此外,还有一种 //!的注释,用于注释说明整个 crate 的功能, 它下面可以不声明任何代码。

 // 使用 cargo test 会执行 # Examples 下的代码,从而保证示例代码的准确性;
/// Adds one to the number given.
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = chapter14::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```

pub fn add_one(x: i32) -> i32 {
    // 加一运算
x + 1
}

  控制流

控制流是一种描述程序执行顺序的机制,它决定了程序中语句或表达式的执行顺序。和其他语言一样,Rust 的控制流可以使用 if else, loop,for, while,contine, break 实现。

fn main() {
     let a = 3;
     // if 后面的表达式必须是一个布尔类型,否则将无法编译。
     // if a {
     //     //expected `bool`, found integer
     //     println!("hello world");
     // }

     // 使用 else if 处理多重判断条件
    if a % 2 == 0 {
        println!("x2")
    } else if a % 3 == 0 {
        println!("x3")
    } else if a % 4 == 0 {
        println!("x4")
    } else {
        println!("null")
    }

    // if 本质上是一个表达式
    let r = if a > 0 { true } else { false };
    println!("{}", r);
}

continebreak 通常和 loop,for, while 一起使用。continue: 跳过这个循环阶段的剩余代码,开始下一个循环。break: 直接跳出循环。如果存在多层循环,continuebreak 只能作用与内层循环,如果要跳出外层循环需要使用 loop label

fn main() {
    let (mut count, mut k) = (0, 10);
    'loop_label: loop {
        loop {
            if k == 5 {
                break 'loop_label; // 跳出外层循环
            }
            k = k - 1;
            break;
        }
        count += 1;
    }
    println!("count: {}", count);

    // 使用 break 返回 loop 表达式的值
    let count = loop {
        if count < 10 {
            count += 1
        } else {
            break count;
        }
    };
    println!("count: {}", count);

    // for 循环表达式
    for i in 1..4 {
        println!("{}", i);
    }
}