变量与可变性

变量的可变性

rust中进行变量的声明时,使用let关键字,如:

let x;

这样声明出来的变量是不可变的,不可以进行第二次赋值。 如果需要后续可以修改这个变量,在声明时需要加上mut关键字:

let mut x;

常量

声明常量使用 const 关键字而不是 let,并且必须注明值的类型,且声明时就必须赋值。

// ✅
const THREE_HOURS_IN_SECONDS: u32 = 3 * 60 * 60;

// ❌
const THREE_HOURS_IN_SECONDS: u32;
THREE_HOURS_IN_SECONDS = 3 * 60 * 60

不可变变量与常量有什么区别呢?

首先一个区别是,声明常量时提供的值必须是编译期间就可以计算出来的,而不可变变量虽然不可变,但是声明时可以使用运行时值。 内存布局角度来看,常量一般都是存储在数据段,而变量会在栈区或堆区。

隐藏

rust中,可以使用let定义一个与之前变量同名的新变量,此时同名的第一个变量隐藏了。

fn main() {
	// 定义x
    let x = 5;
	// 再次定义x,此时第一个x被隐藏
    let x = 6;

    {
	    // 再次定义x,但是仅在该作用域内生效
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}"); // 12
    }

    println!("The value of x is: {x}"); // 6
}
请注意文中的用词含义,是隐藏而不是覆盖,因为此时存在真实的两个变量,而不是对第一个变量进行再次赋值。

乍看这个机制看着并不是很有用,特别是在不同作用域中的重名定义,这在其他的编程语言是很常见的。其实该机制的主要作用体现在变量的重复定义时,是可以修改变量的类型的。例如:

// ✅
let spaces = "   ";
let spaces = spaces.len();

这里的spaces标识符所对应的变量从字符串类型变量改成了另一个整数类型变量,在后续的代码中你可以继续使用这个spaces变量名而不用定义一个新的变量名来记录字符串长度,类似于spaces_length。在同一作用域下的重复定义,这种行为在其他的编程语言的编译器眼中一般都种错误。


数据类型

标量类型

整型

Rust 中的整型:

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

isize 和 usize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。

Rust 中的整型字面值:

数字字面值例子
Decimal (十进制)98_222
Hex (十六进制)0xff
Octal (八进制)0o77
Binary (二进制)0b1111_0000
Byte (单字节字符)(仅限于u8)b’A'

整形溢出问题

比方说有一个 u8 ,它可以存放从零到 255 的值。那么当你将其修改为 256 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),这会导致以下两种行为之一的发生。

  1. 当在 debug 模式编译时,Rust 检查这类问题并使程序 panic
  2. 在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码回绕(two’s complement wrapping)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 256 变成 0,值 257 变成 1,依此类推。依赖整型回绕被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping

浮点数

Rust 也有两个原生的 浮点数floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。所有的浮点型都是有符号的。

fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}

布尔类型

正如其他大部分编程语言一样,Rust 中的布尔类型有两个可能的值:true 和 false。Rust 中的布尔类型使用 bool 表示。例如:

fn main() {
    let t = true;

    let f: bool = false; // with explicit type annotation
}

字符

Rust的 char 类型是语言中最原生的字母类型。下面是一些声明 char 值的例子:

fn main() {
    let c = 'z';
    let z: char = 'ℤ'; // with explicit type annotation
    let heart_eyed_cat = '😻';
}

用单引号声明 char 字面量,使用双引号声明字符串字面量。

Rust 的 char 类型的大小为四个字节(four bytes),并代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。

在 Rust 中,带变音符号的字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。

Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。 “使用字符串存储 UTF-8 编码的文本” 中将详细讨论这个主题。

复合类型

复合类型Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。

元组

元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

创建元组的语法如上,元组中的每一个位置都有一个类型,而且这些不同值的类型不必相同的。这个例子中使用了可选的类型注解:

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1); // ✅
    or
    let tup = (500, 6.4, 1); // ✅
}

元组解构:

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

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

访问元组中的元素,下标从0开始:

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}
不带任何值的元组有个特殊的名称,叫做 单元(unit) 元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。

数组

另一个包含多个值的方式是 数组array)。与元组不同,数组中的每个元素的类型必须相同。 声明数组的方式:

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

和go语言一样,数组都是大小固定的,如果需要类slice的可变长数组,rust中使用标准库提供的vector。 一些支持的写法:

// 这里,i32是每个元素的类型。分号之后,数字5表明该数组包含五个元素。
let a: [i32; 5] = [1, 2, 3, 4, 5];
// 变量名为a的数组将包含5个元素,这些元素的值最初都将被设置为3。
let a = [3; 5];

访问数组元素:

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

    let first = a[0];
    let second = a[1];
}

函数

Rust 代码中的函数和变量名使用 snake case 规范风格,所有字母都是小写并使用下划线分隔单词。

fn main() {
    println!("Hello, world!");

    another_function();
}

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

使用fn关键字声明函数。

参数

我们可以定义为拥有 参数parameters)的函数,参数是特殊变量,是函数签名的一部分。当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。技术上讲,这些具体值被称为参数(arguments)。

在函数签名中,必须 声明每个参数的类型。这是 Rust 设计中一个经过慎重考虑的决定:要求在函数定义中提供类型注解,意味着编译器再也不需要你在代码的其他地方注明类型来指出你的意图。而且,在知道函数需要什么类型后,编译器就能够给出更有用的错误消息。

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

语句和表达式

语句Statements)是执行一些操作但不返回值的指令。

fn main() {
    let y = 6; // 语句
    let x = (let y = 6); // ❌
}

let y = 6 语句并不返回值,所以没有可以绑定到 x 上的值。这与其他语言不同,例如 C 和 Ruby,它们的赋值语句会返回所赋的值。在这些语言中,可以这么写 x = y = 6,这样 x 和 y 的值都是 6;Rust 中不能这样写。

表达式(Expressions)计算并产生一个值。大部分 Rust 代码是由表达式组成的。考虑一个数学运算,比如 5 + 6,这是一个表达式并计算出值 11。表达式可以是语句的一部分:语句 let y = 6; 中的 6 是一个表达式,它计算出的值是 6。函数调用是一个表达式。宏调用是一个表达式。用大括号创建的一个新的块作用域也是一个表达式,例如:

fn main() {
    let y = {
        let x = 3;
        x + 1  // 注意这里结尾处没有分号
    };

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

函数返回值

fn main() {
    let x = plus_one(5);

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

fn plus_one(x: i32) -> i32 {
    x + 1 // 表达式形式返回 ✅
    or
    return x + 1; // 语句形式返回 ✅
}

rust中不可以像go一样对返回值命名,使用->并提供返回值类型。

控制流

if表达式

if是表达式而不是语句

if是表达式,可以返回值,所以可以在let的右侧使用它:

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

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

这里的if有两个分支,对应两个表达式,最终返回的就是匹配分支对应表达式的值,所以这里也要求所有分支对应表达式必须返回同类型的值,如果不匹配会报错。

fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" }; // ❌ 两个表达式返回的类型不一致

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

循环

Rust 有三种循环:loopwhile 和 for

loop关键字搭配breakcontinue使用:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        } else {
            continue;
        };
    };

    println!("The result is {result}");
}

如上是一个loop表达式,在loop内部可以通过break产出值。 当然,利用if表达式,也可以这么写:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        break if counter == 10 { counter * 2 } else { continue };
    };

    println!("The result is {result}");
}

循环标签

fn main() {
    let mut count = 0;
    // 给定循环标签counting_up
    '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}");
}

while条件循环

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

for遍历

fn main() {
    let a = [10, 20, 30, 40, 50];

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