Rust程序设计语言-常见编程概念

第二章中提到过,变量默认是不可改变的(immutable)。

变量和可变性

当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。

在尝试改变预设为不可变的值时,产生编译时错误是很重要的,因为这种情况可能导致 bug。如果一部分代码假设一个值永远也不会改变,而另一部分代码改变了这个值,第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 的起因难以跟踪,尤其是第二部分代码只是 有时 会改变值。

Rust 编译器保证,如果声明一个值不会变,它就真的不会变。这意味着当阅读和编写代码时,不需要追踪一个值如何和在哪可能会被改变,从而使得代码易于推导。

不过可变性也是非常有用的。变量只是默认不可变;正如在第二章所做的那样,你可以在变量名之前加 mut 来使其可变。除了允许改变值之外,mut 向读者表明了其他代码将会改变这个变量值的意图。

除了防止出现 bug 外,还有很多地方需要权衡取舍。例如,使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快。对于较小的数据结构,总是创建新实例,采用更偏向函数式的编程风格,可能会使代码更易理解,为可读性而牺牲性能或许是值得的。

变量和常量的区别

不允许改变值的变量,可能会使你想起另一个大部分编程语言都有的概念:常量(constants)。类似于不可变变量,常量是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。

  • 不允许对常量使用 mut,常量不光默认不能变,它总是不能变。
  • 声明常量使用 const 关键字而不是使用 let,并且 必须 注明值的类型。
  • 常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
  • 最后一个区别是,常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。

这是一个声明常量的例子,它的名称是 MAX_POINTS,值是 100,000。(Rust 常量的命名规范是使用下划线分隔的大写字母单词,并且可以在数字字面值中插入下划线来提升可读性):

const MAX_POINTS: u32 = 100_000;

在声明它的作用域之中,常量在整个程序生命周期中都有效,这使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。

将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需修改汇聚于一处的硬编码值。

隐藏(Shadowing)

隐藏与将变量标记为 mut 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 let 关键字,就会导致编译时错误。通过使用 let,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。

mut 与隐藏的另一个区别是,当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,但复用这个名字。

数据类型

在 Rust 中,每一个值都属于某一个 数据类型(data type),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)。

Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。

标量类型

标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。

整型

浮点型

布尔型

字符类型

复合类型

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

元组类型

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

我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:

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

数组类型

另一个包含多个值的方式是 数组(array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。

Rust 中,数组中的值位于中括号内的逗号分隔的列表中:

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

当你想要在栈(stack)而不是在堆(heap)上为数据分配空间(第四章将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector。第八章会详细讨论 vector。

函数如何工作

Rust使用 fn 关键字来声明新函数。
Rust 代码中的函数和变量名使用 snake case 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。这是一个包含函数定义示例的程序:

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

    another_function();
}

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

Rust 中的函数定义以 fn 开始并在函数名后跟一对圆括号。大括号告诉编译器哪里是函数体的开始和结尾。

源码中 another_function 定义在 main 函数 之后;也可以定义在之前。

Rust 不关心函数定义于何处,只要定义了就行。

函数参数

函数也可以被定义为拥有 参数(parameters),参数是特殊变量,是函数签名的一部分。
当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。
parameter:形参,函数的参数
argument:实参,调用函数时传入的具体值

下面被重写的 another_function 版本展示了 Rust 中参数是什么样的:

fn main() {
    another_function(5);
}

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

在函数签名中,必须 声明每个参数的类型。这是 Rust 设计中一个经过慎重考虑的决定:要求在函数定义中提供类型注解,意味着编译器不需要你在代码的其他地方注明类型来指出你的意图。

包含语句和表达式的函数体

函数体由一系列的语句和一个可选的结尾表达式构成。目前为止,我们只介绍了没有结尾表达式的函数,不过你已经见过作为语句一部分的表达式。因为 Rust 是一门基于表达式(expression-based)的语言,这是一个需要理解的(不同于其他语言)重要区别。其他语言并没有这样的区别,所以让我们看看语句与表达式有什么区别以及这些区别是如何影响函数体的。

语句(Statements)是执行一些操作但不返回值的指令。
表达式(Expressions)计算并产生一个值。

语句

语句不返回值。
因此,不能把 let 语句赋值给另一个变量,比如下面的例子尝试做的,会产生一个错误:

fn main() {
    let x = (let y = 6);
}

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

表达式

表达式会计算出一些值,并且你将编写的大部分 Rust 代码是由表达式组成的。

  • 考虑一个简单的数学运算,比如 5 + 6,这是一个表达式并计算出值 11。

  • 表达式可以是语句的一部分:在示例 3-1 中,语句 let y = 6; 中的 6 是一个表达式,它计算出的值是 6。

  • 函数调用是一个表达式。

  • 宏调用是一个表达式。

  • 我们用来创建新作用域的大括号(代码块),{},也是一个表达式,例如:

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

    这个表达式:

    {
        let x = 3;
        x + 1
    }
    

    是一个代码块,它的值是 4。这个值作为 let 语句的一部分被绑定到 y 上。注意结尾没有分号的那一行 x+1,与你见过的大部分代码行不同。

    表达式的结尾没有分号。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值。

具有返回值的函数

函数可以向调用它的代码返回值。
我们并不对返回值命名,但要在箭头(->)后声明它的类型。
在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。
使用 return 关键字和指定值,可从函数中提前返回;但大部分函数隐式的返回最后的表达式。

这是一个有返回值的函数的例子:

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

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

在 five 函数中没有函数调用、宏、甚至没有 let 语句——只有数字 5。
这在 Rust 中是一个完全有效的函数。注意,也指定了函数返回值的类型,就是 -> i32。

让我们看看另一个例子:

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

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

fn plus_one(x: i32) -> i32 {
    x + 1
}

运行代码会打印出 The value of x is: 6。但如果在包含 x + 1 的行尾加上一个分号,把它从表达式变成语句,我们将看到一个错误

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

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

fn plus_one(x: i32) -> i32 {
    x + 1;
}

运行代码会产生一个错误,如下:

error[E0308]: mismatched types
 --> src/main.rs:7:28
  |
7 |   fn plus_one(x: i32) -> i32 {
  |  ____________________________^
8 | |     x + 1;
  | |          - help: consider removing this semicolon
9 | | }
  | |_^ expected i32, found ()
  |
  = note: expected type `i32`
             found type `()`

主要的错误信息,“mismatched types”(类型不匹配),揭示了代码的核心问题。函数 plus_one 的定义说明它要返回一个 i32 类型的值,不过语句并不会返回值,使用空元组 () 表示不返回值
因为不返回值与函数定义相矛盾,从而出现一个错误。

注释

控制流

Rust 代码中最常见的用来控制执行流的结构是 if 表达式和循环。

if 表达式

if 表达式允许根据条件执行不同的代码分支。你提供一个条件并表示 “如果条件满足,运行这段代码;如果条件不满足,不运行这段代码。”

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

所有的 if 表达式都以 if 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 number 的值是否小于 5。

条件 必须 是 bool 值。如果条件不是 bool 值,我们将得到一个错误。

在 let 语句中使用 if

因为 if 是一个表达式,我们可以在 let 语句的右侧使用它,例如在示例 3-2 中:

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

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

示例 3-2:将 if 表达式的返回值赋给一个变量

number 变量将会绑定到表示 if 表达式结果的值上。

记住,代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 if 表达式的值取决于哪个代码块被执行。这意味着 if 的每个分支的可能的返回值都必须是相同类型;

使用循环重复执行

Rust 有三种循环:loop、while 和 for。我们每一个都试试。

使用 loop 重复执行代码

如果将返回值加入你用来停止循环的 break 表达式,它会被停止的循环返回:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

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

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

在循环之前,我们声明了一个名为 counter 的变量并初始化为 0。
接着声明了一个名为 result 来存放循环的返回值。
在循环的每一次迭代中,我们将 counter 变量加 1,接着检查计数是否等于 10。
当相等时,使用 break 关键字返回值 counter * 2。
循环之后,我们通过分号结束赋值给 result 的语句。
最后打印出 result 的值,也就是 20。

while 条件循环

在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 break 停止循环。
Rust 为此内置了一个语言结构,它被称为 while 循环。

fn main() {
    let mut number = 3;

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

        number = number - 1;
    }

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

这种结构消除了很多使用 loop、if、else 和 break 时所必须的嵌套,这样更加清晰。当条件为真就执行,否则退出循环。

使用 for 遍历集合

作为更简洁的替代方案,可以使用 for 循环来对一个集合的每个元素执行一些代码。
更为重要的是,我们增强了代码安全性,并消除了可能由于超出数组的结尾或遍历长度不够而缺少一些元素而导致的 bug。
for 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。

即使是在想要循环执行代码特定次数时,例如示例 3-3 中使用 while 循环的倒计时例子,大部分 Rustacean 也会使用 for 循环。这么做的方式是使用 Range,它是标准库提供的类型,用来生成从一个数字开始到另一个数字之前结束的所有数字的序列。

下面是一个使用 for 循环来倒计时的例子,它还使用了一个我们还未讲到的方法,rev,用来反转 range:

fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Rust程序设计语言》第二版是一本关于Rust编程语言的重要参考书籍。Rust是一种系统级编程语言,其设计目标是提供安全性、并发性和性能,并且易于使用。 这本书不仅适合那些已经有一些编程基础的读者,也适合那些想学习Rust编程的新手。书中首先介绍了Rust的基本概念和语法,然后逐步深入讲解了更高级的主题。 第二版相比第一版进行了全面的更新和修订,以反映Rust生态系统的最新发展。新版引入了更多的示例代码和练习题,有助于读者巩固所学知识。 本书的优点之一是它循序渐进地引导读者了解Rust的各个方面。从基本类型和变量,到所有权和借用,再到并发和异步编程,每个主题都经过精心编排和解释。同时,书中也对常见Rust编程错误进行了说明,并给出了相应的建议和解决方案。 此外,本书还涉及到Rust的标准库和常见的工具链,如包管理器Cargo和测试框架等。这些内容为读者提供了更全面的学习资料,使他们能够更好地编写和管理Rust代码。 总之,以其扎实的内容和清晰的讲解,《Rust程序设计语言》第二版是学习和使用Rust的理想参考书。无论是想要提升自己的编程技能,还是进一步了解系统级编程语言,这本书都是一个不错的选择。 ### 回答2: 《Rust程序设计语言》是一本介绍Rust编程语言的权威教材。它的第二版着重介绍了Rust 2018 Edition的新特性和改进。 书中首先会带领读者了解Rust的基础知识,包括变量、数据类型、函数、常用控制流结构等。接着深入介绍Rust的所有权系统,这是Rust最独特和重要的特性之一。所有权系统使得Rust能够在编译时杜绝大多数内存安全问题,同时保证高效的内存管理。 书中还详细介绍了Rust的并发编程特性,包括线程、锁、通道等,以及如何安全地共享和传递数据。并发编程是现代软件开发中不可或缺的一部分,Rust的并发模型使得开发者能够安全地充分利用多核处理器的性能。 此外,书中还介绍了Rust的错误处理机制,包括常见的panic和Result类型。错误处理是编程中必不可少的一环,Rust提供了优雅且安全的错误处理方式,使得开发者能够更好地处理和处理错误。 在Rust程序设计语言第二版中,还有关于模块化编程、泛型、trait等高级特性的深入讲解。这些特性能够帮助开发者编写更加模块化、复用性更强的代码,提升开发效率和代码可维护性。 总的来说,《Rust程序设计语言第二版》是一本循序渐进、详细全面介绍Rust编程语言和生态系统的教材。通过学习该书,读者可以深入理解Rust设计理念和特性,并能够编写高效、安全、可维护的Rust程序。无论是初学者还是有一定经验的开发者,都能从中受益匪浅。 ### 回答3: 《Rust程序设计语言》第二版是一本关于Rust编程语言的权威参考书籍。这本书由Rust语言核心开发者编写,旨在帮助读者全面了解Rust语言的各个方面,并学会如何使用它来构建高性能、安全稳定的软件。 《Rust程序设计语言》第二版从Rust的基础开始,介绍了语言的特性、语法和常用工具,并深入讲解了所有权、借用、生命周期等独特的Rust概念。读者将学会如何使用模块化和面向对象的编程技术来组织代码,并了解Rust的并发编程模型和内存管理机制。 该书的特点之一是实用性强,通过丰富的代码示例和练习题,读者可以锻炼自己的编程能力,并将所学知识应用到真实的项目中。此外,《Rust程序设计语言》第二版还包含了对Rust生态系统中常用的库和框架的介绍,帮助读者更好地理解和应用这些工具。 与第一版相比,第二版对Rust语言的最新特性和改进进行了更新,并添加了全新的内容,如异步编程和Web开发等。这些对于已经熟悉Rust的开发者来说,是进一步扩展自己的知识的绝佳资料。 总的来说,如果你想系统地学习和掌握Rust语言,深入了解其内部机制,并能够编写高效、安全的软件,那么《Rust程序设计语言》第二版无疑是一个不可或缺的指南,它将为你提供丰富而全面的知识,并帮助你在Rust编程领域取得成功。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值