Rust 之六 语句和表达式、变量与常量、函数

概述

  Rust 的基本语法对于从事底层 C/C++ 开发的人来说多少有些难以理解,虽然官方有详细的文档来介绍,不过内容是相当的多,看起来也费劲。本文通过将每个知识点简化为 一个 DEMO + 每种特性各用一句话描述的形式来简化学习过程,提高学习效率。

所有 DEMO 可以从 https://gitee.com/itexp 获取

基本概念

  在之前的博文 Rust 之四 运算符、标量、元组、数组、字符串、结构体、枚举 我们学习了 Rust 的数据类型,其中很多地方及示例有用的变量、函数等这些 Rust 的代码的基本组成部分,接下来我们就先学习一下 Rust 代码的基本单元。

程序项

  程序项(Item)是 Rust 源代码的组织单元。程序项是 Rust 程序中的顶层结构,构成了 Rust 程序的基本骨架。程序项在编译时就完全确定下来了,通常在执行期间保持结构稳定,并可以驻留在只读内存中。有以下几类程序项:

  • 模块
  • 外部 crate 声明 extern crate
  • use 声明
  • 函数定义
  • 类型定义
  • 结构体定义
  • 枚举定义
  • 联合体定义
  • 常量项
  • 静态项
  • trait 定义
  • 实现
  • 外部块 extern blocks

语句和表达式

  Rust 是一门基于表达式(expression-based)的语言。在 Rust 中,语句(Statements)和表达式(Expressions)是两种基本的代码构建块,它们在 Rust 的控制流和语法中有重要的作用。

  • 语句是执行一些操作但不返回值的指令。变量的定义是语言;函数定义也是语句
    // 变量绑定(声明语句)
    let x = 5; // 声明语句
    
    // 表达式语句(表达式 + 分号)
    println!("Hello!"); // 表达式 `println!` 后加分号变为语句
    
  • 表达式计算并产生一个值。单独的一个字面值是一个表达式;函数调用是一个表达式;宏调用是一个表达式;用大括号创建的一个新的块作用域也是一个表达式
    // 字面量表达式
    42 // 返回整数 42
    
    // 算术表达式
    3 + 2 // 返回 5
    
    // 函数调用(是表达式)
    let x = add(3, 4) // 返回 7
    
    // {} 代码块是一个表达式,但是 let y = {}; 是语句
    let y = {
        let a = 1;
        a + 1 // 代码块的值是 2。如果这里加了分号就变成语句了!
    };
    
    如果代码块的最后一行不加分号,它是一个表达式,返回该行的值。如果加了分号,则变成语句,返回 ()(unit 类型)。
    let a = { 5 };    // a = 5
    let b = { 5; };   // b = ()
    
  • 表达式可以是语句的一部分,一个典型的示例(后续我们再详细分析):
    let result = if x > 10 {
        "Greater than 10"
    } else {
        "Less than or equal to 10"
    };
    

变量与常量

  在编程语言中,变量和常量是两种用于存储数据的基本元素。

变量

  变量是一种用于存储数据的标识符。它代表内存中的一个位置,可以用来存储值(比如数字、字符串、对象等)。

普通变量

  Rust 中是使用 let 关键字来定义一个普通变量,完整格式为 let 变量名: 类型 = 初值;,例如,let x: u32 = 5;变量默认是不可改变的(immutable),可以在变量名前添加 mut 关键字(let mut 变量名: 类型 = 初值;)来使其可变。

/* ========================= 不允许函数外定义普通变量 ========================= */
// let bb: i32 = 1;
// let mut aa = 2;

fn main() {
    /* ============================= 普通变量定义 ============================ */
    let a: i32 = 5;     // 显式定义一个 i32 类型的变量 a,初值为 5
    let a = 5;          // 自动推导类型为 i32
    // a = 6;          // 错误,默认变量 a 是不可变的
    /* ============================= 可变变量定义 ============================ */
    let mut b = 5;  // 自动推导类型为 i32;mut 关键字表示变量 b 是可变的
    println!("Hello, world! a = {}, b = {}", a, b);
    b = 6;          // 修改变量 b 的值
    println!("Hello, world! a = {}, b = {}", a, b);
    /* ============================= 变量的初始化 ============================ */
    let x: i32;     // 可以后续再初始化
    let condition = true;
    if condition {
        x = 10;
    } else {
        x = 11;
    } // 如果缺少 else 分支,则 Rust 会报错,因为 x 可能没有被初始化
    println!("{}", x);
}
  1. 变量名通常是用小写 +下划线来命名;类型 详见上篇博文的数据类型章节(在某些情况下,Rust 会自动推断数据的类型,因此,有时候 类型 可以省略);初值 就是 类型 对应的字面值或者是运算结果或者是函数返回值(在定义时不强制要求立即赋初值,只要在使用前被初始化即可)

  2. 不允许在函数外定义普通变量,只能定义静态变量或者常量

  3. 普通变量通常存储在栈(stack)上,其生命周期通常是局部的,只在其作用域内存在。当作用域结束时,变量会被销毁

静态变量

  Rust 中是使用 static 关键字来定义一个静态变量,完整格式为 static 变量名: 类型 = 初值;,例如,static X: u32 = 5;静态变量默认是不可变的,可以在 变量名 前加 mut 关键(static mut 变量名: 类型 = 初值;)字使其可变。 但是,可变的静态变量也不能直接修改,还需要使用 unsafe 块来进行修改,因为它们通常会在多个线程或其他地方被共享。

/* ========================= 允许函数外定义静态变量 ========================= */
static AA: u32 = 1;
static mut BB: u64 = 2;

fn main() {
    println!("Hello, world! AA = {AA}"); // 这里实际访问下面的静态变量 AA
    unsafe {        // 必须在 unsafe 块中才能访问/修改可变静态变量
        BB = 3;
        // println!("Hello, world! BB = {}", BB); // Rust 2024 中报错
    }
    /* ============================ 不可变静态变量 =========================== */
    static AA: u32 = 3;     // 隐藏了上面的静态变量 AA
    // static AA: u32 = 4; // 错误,不能在同一作用域中定义两个同名的静态变量
    // AA = 4; 			// 错误,默认变量 AA 是不可变的
    println!("Hello, world! AA = {AA}");
    /* ============================= 可变静态变量 ============================ */
    static mut BB:u32 = 5;	// mut 关键字表示变量 BB 是可变的,这里会隐藏上面的静态变量 BB
    unsafe {
        BB = 6;			// 必须在 unsafe 块中才能修改静态变量
    }
    // println!("Hello, world! BB = {}", BB); // 错误,不能在 unsafe 块外访问可变静态变量
}
  1. 变量名通常是用大写 +下划线来命名;必须显式指名 类型类型 详见上篇博文的数据类型章节;初值必须在定义时就显式指定为 类型 对应的字面值或者是运算结果

  2. static mut 是 Rust 的历史包袱,已被标记为不推荐。现代 Rust 强制使用更安全的同步机制(如 Atomic、Mutex、RwLock)。从 Rust 2024 开始,可变静态变量默认会被当做错误来处理

  3. 不能在同一作用域中定义两个同名的静态变量

  4. 静态变量存储在程序的数据段(data segment)中,有固定的内存地址。其生命周期贯穿整个程序的生命周期,它们在程序的启动时被分配内存,并且在程序结束时释放。

常量

  常量(constant)是一个在程序运行期间其值无法改变的变量。它是用来存储不变的数据,通常在程序开始时就被赋予一个固定的值,并且在整个程序的生命周期中都保持这个值。Rust 中使用 const 来定义一个常量,完整格式为 const 常量名: 类型 = 初值;。例如,const YOUR_NAME: u32 = 5;

/* ========================= 允许函数外定义常量 ========================= */
const AA: u32 = 1;
// const mut BB: u64 = 2; // 错误,常量不能是可变的

fn main() {
    println!("Hello, world! AA = {AA}"); // 这里实际访问下面的常量 AA
    /* ============================== 常量定义 =============================== */
    const AA: u32 = 3;     // 隐藏了上面的常量 AA
    // const AA: u32 = 4; // 错误,不能在同一作用域中定义两个同名的常量
    // AA = 4; 			// 错误,默认变量 AA 是不可变的
    println!("Hello, world! AA = {AA}");
}
  1. 常量名通常是用大写 +下划线来命名;必须显式指明 类型类型 详见上篇博文的数据类型章节;初值只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值。

  2. 不允许对常量使用 mut 关键字

  3. 常量可以在函数、结构体、枚举等任何地方,包括全局作用域。

  4. 常量在编译时就被计算并嵌入到程序中,没有固定的内存地址

  5. 不允许在同一作用域内重复定义同名常量

隐藏(遮蔽)

  Rust 允许在同一个作用域中定义一个与之前变量同名的新变量,新的同名变量将会在自己的作用域范围内隐藏(Shadowing)之前的同名变量,当新的同名变量的作用域结束后,旧变量会再次显现(如果作用域没有结束的话)。

fn main() {
    /* ============================ 普通变量隐藏 ============================= */
    let x = 5;
    println!("Hello, world! x = {}", x);    // 输出的 x = 5
    let x = 6;                              // 同一作用域中会隐藏 let x = 5;
    println!("Hello, world! x = {}", x);    // 输出的 x = 6
    {
        let x = 7;                          // 不同作用域定义同名变量,但是实际上不属于 Shadowing 的特性;
        println!("Hello, world! x = {}", x);// 输出的 x = 7
    }
    println!("Hello, world! x = {}", x);    // 输出的 x = 6。因为 let x = 7; 的作用域已经结束了
    /* ============================= 静态变量隐藏 ============================ */
    static X: u32 = 5;                      // 常量与静态变量不能在同一作用域中定义相同的名称
    println!("Hello, world! X = {}", X);    // 输出的 X = 5
    // static X: u32 = 6;                       // 不允许统一作用域定义相同的常量(不支持隐藏)
    {
        static X: u32 = 7;                  // 不同作用域定义同名静态变量,但是实际上不属于 Shadowing 的特性
        println!("Hello, world! X = {}", X);// 输出的 X = 7
    }
    println!("Hello, world! X = {}", X);    // 输出的 X = 5。因为 static X: u32 = 7; 的作用域已经结束了
    /* =============================== 常量隐藏 ============================== */
    const Y: u32 = 5;                       // 常量与静态变量不能在同一作用域中定义相同的名称
    println!("Hello, world! X = {}", Y);    // 输出的 Y = 5
    // const Y: u32 = 6;                       // 不允许统一作用域定义相同的常量(不支持隐藏)
    {
        const Y: u32 = 7;                   // 不同作用域定义同名常量,但是实际上不属于 Shadowing 的特性
        println!("Hello, world! X = {}", Y);// 输出的 Y = 7
    }
    println!("Hello, world! X = {}", Y);    // 输出的 Y = 5。因为 const Y: u32 = 7; 的作用域已经结束了
}

  隐藏(遮蔽)适用于所有数据类型,包括但不限于:基本类型(i32f64bool 等)、字符串和切片(String&str)、结构体和枚举(自定义类型)、集合类型(VecHashMap)、引用和智能指针(&TBox<T>)、函数和闭包。

  1. 常量与静态变量不能在同一作用域中定义相同的名称,在同一作用域中也不能定义同名的常量和变量以及静态变量和变量

  2. 新变量可以与之前的变量类型和值都可以不同

函数

  在编程语言中,函数是一个非常重要的概念,它的主要作用是将特定的代码逻辑封装在一个独立的模块中,从而实现代码的复用、模块化和抽象化。函数通常接收输入参数、执行某些操作,并返回一个结果。

fn function_name(参数名 1: 类型, 参数名 2: 类型, 后续参数名及类型) -> 返回值类型 {
	// {} 之内的内容称为函数体
	
	// 调用其他函数
	function_name_2();
}

  在 Rust 中,通过输入 fn 关键字后面跟着函数名和一对圆括号来定义函数签名,然后再紧跟一对大括号来定义由一系列的语句和一个可选的结尾表达式构成的函数体。大括号告诉编译器哪里是函数体的开始和结尾来完整定义一个函数。
在这里插入图片描述

  1. Rust 代码中的函数和变量名使用 snake case 规范风格

  2. 使用 函数名(参数1, 参数2, 更多参数) 来调用我们定义过的任意函数,并且 Rust 对调用的函数的实现的位置没有要求,可以在调用之前,也可以在调用之后。相比 C 语言要求调用函数的之前必须已经定义

  3. fn main() { } 函数是很多可执行程序的入口点

参数

  函数可以有参数也可以没有参数,参数是特殊变量,是函数签名的一部分,通常称为形参(Parameter)。如果选择有参数,则必须显示指明参数的类型。当调用拥有参数(形参)的函数时,可以为这些参数提供具体的值,这个值被称为实参(Arguments)
在这里插入图片描述

标量类型参数

  在 Rust 中,标量类型作为函数参数时,既可以通过值传递也可以通过引用传递。在值传递时会发生值拷贝,而引用传递时函数接收到的是原始数据的引用(地址),而不是数据的拷贝。

/* ============================= 标量类型作为参数 ================================ */
// 值传递
fn func_scalar1(width: u32, height: f32, flag: bool, tip: char) {
    println!("width: {}", width);
    println!("height: {}", height);
    println!("flag: {}", flag);
    println!("tip: {}", tip);
}
// 不可变引用传递
fn func_scalar2(width: &u32, height: &f32, flag: &bool, tip: &char) {
    println!("width: {}", width);
    println!("height: {}", height);
    println!("flag: {}", flag);
    println!("tip: {}", tip);
}
// 可变引用传递
fn func_scalar3(width: &mut u32, height: &mut f32, flag: &mut bool, tip: &mut char) {
    *width += 1;
    *height += 1.0;
    *flag = !*flag;
    *tip = 'B';
    println!("width: {}", width);
    println!("height: {}", height);
    println!("flag: {}", flag);
    println!("tip: {}", tip);
}
/* ================================ 无参数 ================================== */
fn print_five() {
    println!("five!");
}

fn main() {
    /* ============================== 无参数 ================================ */
    print_five();
    /* ========================= 标量类型作为参数 ============================ */
    let mut width = 30;
    let mut height = 50.0;
    let mut flag = true;
    let mut tip = 'A';
    func_scalar1(width, height, flag, tip);
    func_scalar2(&width, &height, &flag, &tip);
    func_scalar3(&mut width, &mut height, &mut flag, &mut tip);
}
  1. 如果采用可变引用传递,引用的标量类型本身也要是可变的才可以修改

  2. 通过 * 解引用后就可以访问元素标量类型的值

复合类型参数

  在 Rust 中,复合类型作为函数参数时,既可以通过值传递也可以通过引用传递,对于数组类型还可以通过切片进行传递。在值传递时,如果复合类型中的元素是 Copy 类型(如 i32),按值传递会复制数据;而非 Copy 类型(如 String)按值传递会转移所有权。

/* ================================ 值传递 ================================== */
fn func_compound1(tup1: (u32, u32), arr1: [i32; 3], tup_2: (String, u32), arr_2: [String; 3]) {
    println!("arr1: {:?}", arr1);
    println!("tup1: {:?}", tup1);
    println!("tup_2: {:?}", tup_2);
    println!("arr_2: {:?}", arr_2);
}
/* ============================ 不可变引用传递 =============================== */
fn func_compound2(tup2: &(u32, u32), arr2: &[i32; 3], tup_2: &(String, u32), arr_2: &[String; 3]) {
    println!("arr2: {:?}", arr2);
    println!("tup2: {:?}", tup2);
    println!("tup_2: {:?}", tup_2);
    println!("arr_2: {:?}", arr_2);
}
/* ============================= 可变引用传递 ================================ */
fn func_compound3(tup3: &mut (u32, u32), arr3: &mut [i32; 3], tup_3: &mut (String, u32), arr_3: &mut [String; 3]) {
    tup3.0 += 1;
    arr3[0] += 1;
    tup_3.0.push_str("1");
    arr_3[0].push_str("1");
    println!("arr3: {:?}", arr3);
    println!("tup3: {:?}", tup3);
    println!("tup2: {:?}", tup_3);
    println!("arr_3: {:?}", arr_3);
}
/* ============================ 不可变切片传递 =============================== */
fn func_compound4(arr4: &[i32], arr_4: &[String]) {
    println!("arr4: {:?}", arr4);
    println!("arr_4: {:?}", arr_4);
}
/* ============================= 可变切片传递 ================================ */
fn func_compound5(arr5: &mut [i32], arr_5: &mut [String]) {
    arr5[0] += 1;
    arr_5[0].push_str("1");
    println!("arr5: {:?}", arr5);
    println!("arr_5: {:?}", arr_5);
}

fn main() {
    /* ============================== 值传递 ================================ */
    let tup1 = (30, 50);
    let arr1 = [1, 2, 3];
    let tup_1 = (String::from("tup_1"), 1);
    let arr_1 = [String::from("1"), String::from("2"), String::from("3")];
    func_compound1(tup1, arr1, tup_1, arr_1);
    println!("tup1 = {:?}", tup1);
    println!("arr1 = {:?}", arr1);
    // println!("tup_1 = {:?}", tup_1);  // 错误,tup_1 不能再使用,因为所有权已经转移
    // println!("arr_1 = {:?}", arr_1);    // 错误,arr_1 不能再使用,因为所有权已经转移
    /* ========================== 不可变引用传递 ============================= */
    let tup2 = (30, 50);
    let arr2 = [1, 2, 3];
    let tup_2 = (String::from("tup_2"), 2);
    let arr_2 = [String::from("1"), String::from("2"), String::from("3")];
    func_compound2(&tup2, &arr2, &tup_2, &arr_2);
    println!("tup2 = {:?}", tup2);
    println!("arr2 = {:?}", arr2);
    println!("tup_2 = {:?}", tup_2);
    println!("arr_2 = {:?}", arr_2);
    /* =========================== 可变引用传递 ============================== */
    let mut tup3 = (30, 50);
    let mut arr3 = [1, 2, 3];
    let mut tup_3 = (String::from("tup_3"), 3);
    let mut arr_3 = [String::from("1"), String::from("2"), String::from("3")];
    func_compound3(&mut tup3, &mut arr3, &mut tup_3, &mut arr_3);
    println!("tup3 = {:?}", tup3);
    println!("arr3 = {:?}", arr3);
    println!("tup_3 = {:?}", tup_3);
    println!("arr_3 = {:?}", arr_3);
    /* =========================== 不可变切片传递 ============================ */
    let arr4 = [1, 2, 3];
    let arr_4 = [String::from("1"), String::from("2"), String::from("3")];
    func_compound4(&arr4[1..2], &arr_4[1..2]);
    println!("arr4 = {:?}", arr4);
    println!("arr_4 = {:?}", arr_4);
    /* ============================ 可变切片传递 ============================= */
    let mut arr5 = [1, 2, 3];
    let mut arr_5 = [String::from("1"), String::from("2"), String::from("3")];
    func_compound5(&mut arr5[1..2], &mut arr_5[1..2]);
    println!("arr5 = {:?}", arr5);
    println!("arr_5 = {:?}", arr_5);
}
  1. 当数组作为函数参数时必须明确以 [元素类型; 元素个数] 格式指定类型和长度

  2. 元组不支持切片

结构体参数

  在 Rust 中,结构体作为函数的参数传递时,主要有值传递和引用传递这两种方式。在值传递时,如果结构体中的元素(如 i32)或者结构体本身实现了 Copy 特性,按值传递会复制数据;而没有 Copy 特性(如 String)按值传递会转移所有权。

// 点
struct Point {
    x: u32,
    y: u32,
}

// 圆
struct Circle {
    center: Point,
    radius: u32,
}

/* ================================ 值传递 ================================== */
fn func_struct1(pot: Point, mut crl: Circle) {
    // pot.x += 1;
    // pot.y += 1;
    crl.center.x += 1;
    crl.center.y += 1;
    crl.radius += 1;
    println!("func_struct1: x = {}, y = {} rd = {}", crl.center.x, crl.center.y, crl.radius);
    println!("func_struct1: x = {}, y = {}", pot.x, pot.y);
}
/* ============================= 不可变引用传递 ============================== */
fn func_struct2(pot: &Point) {
    // pot.x += 1; // 错误:不可变引用不能修改值
    // pot.y += 1; // 错误:不可变引用不能修改值
    println!("func_struct1: x = {}, y = {}", pot.x, pot.y);
}
/* ============================== 可变引用传递 =============================== */
fn func_struct3(pot: &mut Point) {
    pot.x += 1;
    pot.y += 1;
    println!("func_struct1: x = {}, y = {}", pot.x, pot.y);
}

fn main() {
    /* ============================== 值传递 ================================ */
    let pot1 = Point { x: 1, y: 2 };
    let crl1 = Circle { center: Point { x: 3, y: 4 }, radius: 5 };
    func_struct1(pot1, crl1);
    // println!("main: x = {}, y = {}", pot1.x, pot1.y); // 错误:pot1的值已经被移动,不能再使用
    /* =========================== 不可变引用传递 ============================ */
    let pot2 = Point { x: 3, y: 4 };
    func_struct2(&pot2);
    println!("main: x = {}, y = {}", pot2.x, pot2.y);
    /* ============================ 可变引用传递 ============================= */
    let mut pot3 = Point { x: 5, y: 6 };
    func_struct3(&mut pot3);
    println!("main: x = {}, y = {}", pot3.x, pot3.y);
}

字符串参数

  在 Rust 中,字符串作为函数的参数传递时,主要有值传递、引用传递、切片传递这三种方式。如果采用值传递,则字符串的所有权会被转移。若需要避免所有权转移,可使用引用。

/* ================================ 值传递 ================================== */
fn func_string1(s1: String) {
    println!("s1 = {}", s1);
}
/* ============================= 不可变引用传递 ============================== */
fn func_string2(s2: &String) {
    println!("s2 = {}", s2);
}
/* ============================== 可变引用传递 =============================== */
fn func_string3(s3: &mut String) {
    println!("s3 = {}", s3);
    s3.push_str(" world");
}
/* ============================= 不可变切片传递 ============================== */
fn func_string4(s4: &str) {
    println!("s4 = {}", s4);
}
/* ============================== 可变切片传递 =============================== */
fn func_string5(s5: &mut str) {
    s5.make_ascii_uppercase();
    println!("s5 = {}", s5);
}

fn main() {
    /* ============================== 值传递 ================================ */
    let s1 = String::from("hello");
    func_string1(s1.clone());
    // println!("s1 = {}", s1); // 这里会报错,因为 s1 的所有权已经转移到 func_string1 函数中
    /* =========================== 不可变引用传递 ============================ */
    let s2 = String::from("hello");
    func_string2(&s2);
    println!("s2 = {}", s2);
    /* ============================ 可变引用传递 ============================= */
    let mut s3 = String::from("hello");
    func_string3(&mut s3);
    println!("s3 = {}", s3);
    /* =========================== 不可变切片传递 ============================ */
    let s4 = String::from("hello");
    func_string4(&s4);
    println!("s4 = {}", s4);
    let s4_1 = "Hello world";
    func_string4(s4_1);
    println!("s4_1 = {}", s4_1);
    /* ============================ 可变切片传递 ============================= */
    let mut s5 = String::from("hello");
    func_string5(&mut s5[0..2]);
    println!("s5 = {}", s5);
}

枚举参数

  在 Rust 中,枚举作为函数的参数传递时,主要有值传递、引用传递这三种方式。如果采用值传递,则枚举的所有权会被转移。若需要避免所有权转移,可使用引用。

/* ================================ 枚举定义 ================================ */
enum Msg<'a> {
    Quit,                       // 可以是无数据类型的普通值
    Move { x: i32, y: i32 },    // 可以是具名字段(结构体形式)
    Write(String),              // 单个数据(元组形式)
    Apps(u8, u8, u8),           // 多个数据(元组形式)
    Text(&'a str),              // 引用外部字符串
    Tips(&'a mut String),       // 可变引用外部字符串
    Size(&'a mut i32),          // 可变引用
}

/* =========================== 枚举作为函数参数 ============================== */
fn process_msg1(msg: Msg) {
    match msg {
        Msg::Quit => println!("Quit msg received"),
        Msg::Move { x, y } => println!("Move to coordinates ({}, {})", x, y),
        Msg::Write(text) => println!("Write msg: {}", text),
        Msg::Apps(a, b, c) => println!("Apps ({}, {}, {})", a, b, c),
        Msg::Text(text) => println!("Text msg: {}", text),
        Msg::Tips(text) => text.push_str(" - Tips"), // 可变引用外部字符串
        Msg::Size(size) => {
            *size += 1;
            println!("Size msg: {}", size);
        }
    }
}
/* ========================= 不可变引用作为函数参数 =========================== */
fn process_msg2(msg: &Msg) {
    match msg {
        Msg::Quit => println!("Quit msg received"),
        Msg::Move { x, y } => println!("Move to coordinates ({}, {})", x, y),
        Msg::Write(text) => println!("Write msg: {}", text),
        Msg::Apps(a, b, c) => println!("Apps ({}, {}, {})", a, b, c),
        Msg::Text(text) => println!("Text msg: {}", text),
        // Msg::Tips(text) => text.push_str(" - Tips"), // 无法修改
        // Msg::Size(size) => {
        //     *size += 1;              // 无法修改
        //     println!("Size msg: {}", size);
        // }
        _ => println!("Unknown msg"),   // 忽略其他情况
    }
}
/* ========================== 可变引用作为函数参数 ============================ */
fn process_msg3(msg: &mut Msg) {
    match msg {
        Msg::Quit => println!("Quit msg received"),
        Msg::Move { x, y } => println!("Move to coordinates ({}, {})", x, y),
        Msg::Write(text) => println!("Write msg: {}", text),
        Msg::Apps(a, b, c) => {
            *a += 1;
            *b += 1;
            *c += 1;
            println!("Apps ({}, {}, {})", a, b, c)
        },
        Msg::Text(text) => println!("Text msg: {}", text),
        Msg::Tips(text) => text.push_str(" - Tips"),
        Msg::Size(size) => {
            **size += 1;    // 通过双重解引用修改值
            println!("Size msg: {}", size);
        }
    }
}

fn main() {
    /* ========================= 枚举作为函数参数 ============================ */
    let quit_msg = Msg::Quit;
    let move_msg = Msg::Move { x: 10, y: 20 };
    let write_msg = Msg::Write(String::from("Hello"));
    let cc_msg = Msg::Apps(1, 2, 3);
    let text_msg = Msg::Text("Hello, world!");
    process_msg1(quit_msg);
    process_msg1(move_msg);
    process_msg1(write_msg);
    process_msg1(cc_msg);
    process_msg1(text_msg);
    // if let Msg::Quit = quit_msg {   // 错误,因为 quit_msg 已经被移动到 process_msg1 函数中
    //     println!("Quit msg is equal to Quit");
    // } else {
    //     println!("Quit msg is not equal to Quit");
    // }
    /* ======================= 枚举的引用作为函数参数 ========================= */
    let quit_msg = Msg::Quit;
    let move_msg = Msg::Move { x: 10, y: 20 };
    let write_msg = Msg::Write(String::from("Hello"));
    let cc_msg = Msg::Apps(1, 2, 3);
    process_msg2(&quit_msg);
    process_msg2(&move_msg);
    process_msg2(&write_msg);
    process_msg2(&cc_msg);
    if let Msg::Quit = quit_msg {   // 这里仍然可以使用 quit_msg
        println!("Quit msg is equal to Quit");
    } else {
        println!("Quit msg is not equal to Quit");
    }
    /* ======================== 可变引用作为函数参数 ========================== */
    let mut size = 10;
    let mut size_msg = Msg::Size(&mut size);
    process_msg3(&mut size_msg);
    println!("Size msg: {}", size);
    let mut tips = String::from("Hello");
    let mut tips_msg = Msg::Tips(&mut tips);
    process_msg3(&mut tips_msg);
    // println!("Tips msg: {:?}", tips_msg);
}
  1. 如果枚举变体中包含引用(如 &str&其他数据类型),需要显示指明 'a 以确保传递给函数的引用的生命周期是有效的

  2. 如果使用枚举的不可变引用,则无法通过它修改内部的任何数据。 即使要修改的内部数据本身是可变的也不行(无法穿透访问内部可变性)。

返回值

  函数可以向调用它的代码返回值也可以不返回值。我们并不对返回值命名,但要在箭头 -> 后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。
在这里插入图片描述

  1. 使用 return [可选的值] 关键字和指定值,可从函数中提前返回

  2. 大部分函数隐式的返回最后的表达式

标量类型返回值

  在 Rust 中,函数可以返回标量类型的值,也可以返回标量类型的引用。由于标量类型实现了 Copy 特性,返回时会自动复制值,无需担心所有权转移,而返回引用时要确保返回的引用指向的数据在引用生命周期内有效。

/* ========================== 标量类型作为返回值 ============================= */
fn add_two(x: i32) -> i32 {
    x + 2
}

fn divide(x: f64, y: f64) -> f64 {
    x / y
}

fn is_even(x: i32) -> bool {
    x % 2 == 0
}

fn first_char(s: &str) -> char {
    s.chars().next().unwrap()
}
/* ======================== 标量类型的引用作为返回值 ========================== */
fn get_number() -> &'static i32 {
    static NUM: i32 = 10;  // 使用静态变量
    &NUM
}
// 实际为 fn get_ref<'a>(input: &'a i32) -> &'a i32,Rust 自动推断了,因此省略
fn get_ref(input: &i32) -> &i32 {
    input
}

fn main() {
    /* ========================= 标量类型作为返回值 ========================== */
    let result = add_two(5);
    println!("Result: {}", result);     // 输出:Result: 7
    let result = divide(10.0, 2.0);
    println!("Result: {}", result);     // 输出:Result: 5
    let result = is_even(4);
    println!("Is even: {}", result);    // 输出:Is even: true
    let result = first_char("hello");
    println!("First character: {}", result);    // 输出:First character: h
    /* ====================== 标量类型的引用作为返回值 ======================== */
    let result = get_number();
    println!("The number is: {}", result);  // 输出:The number is: 10
    let num = 10;
    let r = get_ref(&num);
    println!("get_ref: {}", r);              // 输出: 10
}
  1. 当函数签名中涉及多个引用且生命周期关系不明确时,需手动标注

元组返回值

  在 Rust 中,函数可以返回元组类型,也可以返回元组类型的引用。但是,如果元组中包含所有权类型(如 String、Vec),返回时会转移所有权。而且如果元组中包含了引用,或者返回元组的引用时需要添加 'a 标注声明周期。

/* ========================== 多类型元素的元组 =============================== */
fn get_full_info() -> (String, i32, bool, f64) {
    let name = String::from("Bob"); // 注意所有权问题
    let age = 25;
    let is_student = true;
    let gpa = 3.7;
    (name, age, is_student, gpa)
}
/* ========================= 包含不可变引用的元组 ============================= */
fn get_ref_tuple<'a>(s: &'a str, n: &'a i32) -> (&'a str, &'a i32) {
    (s, n)
}
/* ========================= 包含可变引用的元组 ============================== */
fn swap_values<'a>(a: &'a mut i32, b: &'a mut i32) -> (&'a mut i32, &'a mut i32) {
    std::mem::swap(a, b);
    (a, b)
}
/* ========================= 元组的不可变引用 ============================== */
fn get_tuple_ref<'a>(tuple: &'a (i32, String)) -> &'a (i32, String) {
    tuple // 直接返回参数的引用
}
/* ========================= 元组的可变引用 ============================== */
fn get_mutable_tuple<'a>(tuple: &'a mut (i32, String)) -> &'a mut (i32, String) {
    tuple.0 += 1;               // 修改元组的第一个元素
    tuple.1.push_str(" World"); // 修改元组的第二个元素
    tuple                       // 直接返回参数的可变引用
}

fn main() {
    /* ======================== 多类型元素的元组 ============================= */
    let (name, age, is_student, gpa) = get_full_info();
    println!("Name: {}, Age: {}, Is Student: {}, GPA: {}", name, age, is_student, gpa);
    /* ====================== 包含不可变引用的元组 =========================== */
    let s = "hello";
    let num = 5;
    let (text, value) = get_ref_tuple(s, &num);
    println!("{} - {}", text, value);
    /* ======================= 包含可变引用的元组 ============================ */
    let mut x = 10;
    let mut y = 20;
    let (new_x, new_y) = swap_values(&mut x, &mut y);
    *new_x += 1;
    *new_y += 1;
    println!("swap: x={}, y={}", new_x, new_y); // x=21, y=11
    /* ===================== 元组的不可变引用 ============================= */
    let my_tuple = (42, String::from("Rust"));
    let tuple_ref = get_tuple_ref(&my_tuple);
    println!("元组引用: {:?}", tuple_ref); // (42, "Rust")
    /* ===================== 元组的可变引用 ============================= */
    let mut my_mutable_tuple = (10, String::from("Hello"));
    let mutable_tuple_ref = get_mutable_tuple(&mut my_mutable_tuple);
    println!("可变元组引用: {:?}", mutable_tuple_ref);  // (11, "Hello World")
    mutable_tuple_ref.0 += 1;               // 修改可变元组引用的第一个元素
    mutable_tuple_ref.1.push_str("!!!");    // 修改可变元组引用的第二个元素
    println!("修改后的可变元组引用: {:?}", mutable_tuple_ref);  // (12, "Hello World!!!")
}
  1. 返回空元组 ()(即单元类型)的函数表示“无返回值”
    fn do_nothing() -> () {
        // 函数无显式返回值时,默认返回 ()
    }
    

数组返回值

  在 Rust 中,函数可以返回数组类型,也可以返回数组类型的引用,还可以返回数组的切片。但是,如果数组中包含所有权类型(如 String、Vec),返回时会转移所有权。而且如果数组中包含了引用,或者返回数组的引用时需要添加 'a 标注声明周期。

/* ============================= 固定长度数组 ================================ */
fn get_array() -> [i32; 3] {
    let arr = [1, 2, 3];  // 创建一个长度为 3 的数组
    arr  // 返回数组,由于是标量类型,所以数组会被拷贝
}
/* ========================== 数组的不可变引用 =============================== */
// 实际为 fn get_array_ref<'a>(arr: &'a [i32; 3]) -> &'a [i32; 3],
// 由于 Rust 的自动生命周期推导,所以可以省略生命周期参数
fn get_array_ref(arr: &[i32; 3]) -> &[i32; 3] {
    arr  // 返回对输入数组的引用
}
/* ============================ 数组的可变引用 =============================== */
// 实际为 fn increment_array<'a>(arr: &'a mut [i32; 3]) -> &'a mut [i32; 3],
// 由于 Rust 的自动生命周期推导,所以可以省略生命周期参数
fn increment_array(arr: &mut [i32; 3]) -> &mut [i32; 3] {
    for num in arr.iter_mut() {
        *num += 1;
    }
    arr
}
/* ========================== 数组的不可变切片 =============================== */
// 实际为 fn get_slice<'a>(arr: &'a [i32; 5]) -> &'a [i32],
// 由于 Rust 的自动生命周期推导,所以可以省略生命周期参数
// 切片是对数组的一个视图,返回的是数组的一部分
// 切片的长度不需要在编译时确定,可以在运行时动态计算
// 切片的长度可以是任意值,只要在数组的范围内
fn get_slice(arr: &[i32; 5]) -> &[i32] {
    &arr[1..4] // 返回数组的中间3个元素的切片
}
/* ========================== 数组的可变切片 =============================== */
// 实际为 fn get_mut_slice<'a>(arr: &'a mut [i32; 5]) -> &'a mut [i32],
// 由于 Rust 的自动生命周期推导,所以可以省略生命周期参数
// 可变切片是对数组的一个可变视图,返回的是数组的一部分
// 可变切片的长度不需要在编译时确定,可以在运行时动态计算
// 可变切片的长度可以是任意值,只要在数组的范围内
fn get_mut_slice(arr: &mut [i32; 5]) -> &mut [i32] {
    &mut arr[1..4] // 返回数组的中间3个元素的可变切片
}
/* ========================== 不可变引用的数组 =============================== */
// 不能省略 'a,因为引用的生命周期必须与数组的生命周期相同
fn get_ref_array<'a>(x: &'a i32, y: &'a i32) -> [&'a i32; 2] {
    [x, y]
}
/* ========================== 可变引用的数组 =============================== */
// 不能省略 'a,因为引用的生命周期必须与数组的生命周期相同
fn get_mut_ref_array<'a>(x: &'a mut i32, y: &'a mut i32) -> [&'a mut i32; 2] {
    [x, y]
}

fn main() {
    /* =========================== 固定长度数组 ============================== */
    let array = get_array();
    println!("{:?}", array);  // 输出:[1, 2, 3]
    /* ========================= 数组的不可变引用 ============================ */
    let arr = [1, 2, 3];
    let arr_ref = get_array_ref(&arr);
    println!("{:?}", arr_ref);  // 输出:[1, 2, 3]
    /* ========================= 数组的可变引用 ============================ */
    let mut arr = [1, 2, 3];
    let arr_ref = increment_array(&mut arr);
    println!("increment_array: {:?}", arr_ref);  // 输出 [2, 3, 4]
    arr_ref[0] = 100;                      // 修改 arr_ref 的第一个元素
    println!("修改后数组: {:?}", arr_ref);  // 输出 [100, 3, 4]
    println!("原数组: {:?}", arr);          // 输出 [100, 3, 4]
    /* ========================= 数组的不可变切片 ============================ */
    let arr = [1, 2, 3, 4, 5];
    let slice = get_slice(&arr);
    println!("切片内容: {:?}", slice); // 输出 [2, 3, 4]
    /* ========================== 数组的可变切片 ============================= */
    let mut arr = [1, 2, 3, 4, 5];
    let slice = get_mut_slice(&mut arr);
    println!("get_mut_slice: {:?}", slice);  // 输出 [2, 3, 4]
    slice[0] = 100;                     // 修改切片的第一个元素
    println!("切片内容: {:?}", slice);  // 输出 [100, 3, 4]
    println!("原数组: {:?}", arr);      // 输出 [1, 100, 3, 4, 5]
    /* ========================= 不可变引用的数组 ============================ */
    let a = 10;
    let b = 20;
    let arr = get_ref_array(&a, &b);
    println!("数组内容: {:?}", arr); // 输出 [10, 20]
    /* ========================== 可变引用的数组 ============================= */
    let mut a = 10;
    let mut b = 20;
    let arr = get_mut_ref_array(&mut a, &mut b);
    println!("get_mut_ref_array: {:?}", arr); // 输出 [10, 20]
    *arr[0] = 100;                     // 修改数组的第一个元素
    println!("修改后: {:?}", arr);      // 输出 [100, 20]
}

结构体返回值

  在 Rust 中,函数可以直接返回结构体类型,也可以直接返回结构体的引用。如果枚举中包含引用或者返回枚举值的引用时,需要为结构体和函数添加 'a 标注生命周期,以确保引用在其数据有效期间内使用。

struct Person<'a> {
    name: String,
    age: u32,
    content: &'a str,   // 包含引用类型
}
/* ============================= 直接返回结构体 ============================== */
// 函数签名中的 '_ 让编译器自动推导生命周期,由于 Rust 可以自动推导,因此可以省略
fn create_person(name: String, age: u32, content: &str) -> Person<'_> {
    Person { name, age, content } // 变量名和结构体字段名相同,可以简写
}
/* ========================== 返回结构体的不可变引用 ========================== */
fn get_person<'a>(person: &'a Person) -> &'a Person<'a> {
    person
}
/* ========================== 返回结构体的可变引用 ========================== */
fn get_person_mut<'a>(person: &'a mut Person<'a>) -> &'a mut Person<'a> {
    person.age += 1; // 修改可变引用的值
    person
}

fn main() {
    /* ========================== 直接返回结构体 ============================= */
    let person = create_person("Alice".to_string(), 30, "Some content");
    println!("Name: {}, Age: {}, Content: {}", person.name, person.age, person.content);
    /* ========================= 返回结构体的引用 ============================ */
    let person = Person {
        name: String::from("Alice"),
        age: 30,
        content: "Some content",
    };
    let person_ref = get_person(&person);
    println!("Name: {}, Age: {}, Content: {}", person_ref.name, person_ref.age, person_ref.content);
    /* ========================= 返回结构体的可变引用 ======================== */
    let mut person = Person {
        name: String::from("Alice"),
        age: 30,
        content: "Some content",
    };
    let person_ref_mut = get_person_mut(&mut person);
    person_ref_mut.age += 1; // 修改可变引用的值
    println!("Name: {}, Age: {}, Content: {}", person_ref_mut.name, person_ref_mut.age, person_ref_mut.content);
}
  1. 结构体的定义中若包含引用字段(如 &str&[T]&T 等),必须显式声明生命周期参数 'a。生命周期 'a 表示:结构体实例的生命周期不能超过其引用字段指向的数据的生命周期。

字符串返回值

  在 Rust 中,字符串作为函数返回值时,可以返回 String 类型或者 String 的引用,也可以字符串切片 &str 类型,还可以返回静态字符串 &'static str 类型。

/* ============================ 返回 String 类型 ============================= */
fn create_string(is_mut: bool) -> String {
    if is_mut {
        let mut s = String::from("Hello");
        s.push_str(", world!");  // 修改字符串
        s  // 所有权会从函数中转移到调用者
    } else {
        let s = String::from("Hello, world!");
        s  // 所有权会从函数中转移到调用者
    }
}
/* ============================ 返回 &String 类型 =============================== */
fn get_string(s: &String) -> &String {
    s // 返回不可变引用
}
/* ============================ 返回 &mut String 类型 ============================= */
fn get_mut_string(s: &mut String) -> &mut String {
    s.push_str(", world!"); // 修改传入的 String
    s // 返回可变引用
}
/* ============================ 返回 &str 类型 =============================== */
fn extract_substring<'a>(s: &'a str) -> &'a str {
    &s[0..5]  // 返回传入字符串的前五个字符的切片
}
/* ========================== 返回 &mut str 类型 ============================= */
fn extract_substring_mut<'a>(s: &'a mut str) -> &'a mut str {
    &mut s[0..5]  // 返回传入字符串的前五个字符的切片
}
/* ======================== 返回 &'static str 类型 =========================== */
fn get_static_str() -> &'static str {
    "Static string"  // 这个字符串的生命周期是静态的
}

fn main() {
    /* ========================== 返回 String 类型 =========================== */
    let result = create_string(false);
    println!("create_string(false): {}", result);   // 输出: Hello, world!
    let mut result_mut = create_string(true);
    println!("create_string(true):{}", result_mut); // 输出: Hello, world!
    result_mut.push_str(" Rust!");
    println!("result_mut: {}", result_mut);         // 输出: Hello, world! Rust!
    /* ========================= 返回 &String 类型 =========================== */
    let my_string_ref = String::from("Hello, world!");
    let s_ref = get_string(&my_string_ref);
    println!("get_string: {}", s_ref); // 输出: Hello, world!
    /* ========================= 返回 &mut String 类型 ======================= */
    let mut my_string = String::from("Hello");
    let s_ref = get_mut_string(&mut my_string); 
    println!("get_mut_string: {}", s_ref); // 输出: Hello, world!
    s_ref.push_str(", Rust!"); // 修改传入的 String
    println!("s_ref: {}", s_ref); // 输出: Hello, world!, Rust!
    println!("my_string: {}", my_string); // 输出: Hello, world!, Rust!
    /* =========================== 返回 &str 类型 ============================ */
    let text = String::from("Hello, world!");
    let substring = extract_substring(&text);
    println!("extract_substring: {}", substring);   // 输出: Hello
    /* ========================= 返回 &mut str 类型 ========================== */
    let mut text_mut = String::from("Hello, world!");
    let substring_mut = extract_substring_mut(&mut text_mut);
    println!("extract_substring_mut: {}", substring_mut);  // 输出: Hello
    substring_mut.make_ascii_uppercase();
    println!("substring_mut {}", substring_mut);  // 输出: HELLO
    /* ======================== 返回 &'static str 类型 ======================= */
    let s = get_static_str();
    println!("{}", s);          // 输出: Static string
}

枚举返回值

  在 Rust 中,枚举作为函数的返回值时,可以直接返回枚举值,也可以返回枚举值的不可变或可变的引用。如果枚举中包含引用或者返回枚举值的引用时,需要为枚举和函数的生命周期添加 'a 标注,以确保引用在其数据有效期间内使用。

/* ========================== 普通枚举返回值 ============================ */
enum Status {
    Success,
    Error(String),
}

fn check_value(n: i32) -> Status {
    if n >= 0 {
        Status::Success
    } else {
        Status::Error("数值不能为负".to_string())
    }
}

/* ========================== 带引用的枚举返回值 ============================ */
enum TextResult<'a> {
    Found(&'a str),  // 包含引用,需生命周期参数
    NotFound,
}

fn find_keyword<'a>(text: &'a str, keyword: &'a str) -> TextResult<'a> {
    if text.contains(keyword) {
        TextResult::Found(keyword)
    } else {
        TextResult::NotFound
    }
}

/* ========================= 枚举的不可变引用返回值 =========================== */
enum MyEnum {
    A(i32),
    B(String),
}

fn get_enum_ref_immut<'a>(val: bool, enum_a: &'a MyEnum, enum_b: &'a MyEnum) -> &'a MyEnum {
    if val {
        enum_a // 返回 enum_a 的引用
    } else {
        enum_b // 返回 enum_b 的引用
    }
}
/* ========================== 枚举的可变引用返回值 ============================ */
fn get_enum_ref_mut<'a>(val: bool, enum_a: &'a mut MyEnum, enum_b: &'a mut MyEnum) -> &'a mut MyEnum {
    if val {
        enum_a // 返回 enum_a 的可变引用
    } else {
        enum_b // 返回 enum_b 的可变引用
    }
}

fn main() {
    /* =========================== 普通枚举返回值 ============================ */
    let result = check_value(42);
    match result {
        Status::Success => println!("成功"),
        Status::Error(msg) => println!("错误: {}", msg),
    }
    /* ======================== 可变引用作为函数参数 ========================== */
    let text = "Rust is awesome!";
    let result = find_keyword(text, "Rust");
    match result {
        TextResult::Found(s) => println!("找到关键字: {}", s),
        TextResult::NotFound => println!("未找到"),
    }
    /* ======================= 枚举的不可变引用返回值 ========================= */
    let a = MyEnum::A(10);
    let b = MyEnum::B("Hello".to_string());

    let result = get_enum_ref_immut(true, &a, &b);
    match result {
        MyEnum::A(x) => println!("A: {}", x),
        MyEnum::B(s) => println!("B: {}", s),
    }
    /* ======================== 枚举的可变引用返回值 ========================== */
    let mut a = MyEnum::A(10);
    let mut b = MyEnum::B("Hello".to_string());
    let result = get_enum_ref_mut(true, &mut a, &mut b);
    if let MyEnum::A(x) = result {
        *x += 5; // 修改 a 中的值
    }
    match result {
        MyEnum::A(x) => println!("A: {}", x),
        MyEnum::B(s) => println!("B: {}", s),
    }
}

参考

  1. https://kaisery.github.io/trpl-zh-cn/
  2. https://www.programiz.com/rust/function
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZC·Shou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值