Ch 04.03:总结
Ch 04.01:返回值与作用域(Return Values and Scope)
返回值与作用域(Return Values and Scope)
返回值也可以转移所有权。Listing 4-4展示了一个示例,与Listing4-3一样带有类似的注释。
Filename: src/main.rs
1 | fn main() { |
Listing 4-4: Transferring ownership of return values
变量的所有权总是遵循相同的模式:将赋值给另一个变量时移动它。当持有堆中数据的变量离开作用域时,其值将通过drop被清理,除非数据被移动为另一个变量所有。
虽然这样是可以的,但是在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都穿进去再返回来就有点烦人了,除此之外,我们可能想返回函数中产生的一些数据。
我们可以使用元组来返回多个值,如Listing 4-5。
Filename:src/main.rs
1 | fn main() { |
Listing 4-5: Returning ownership of parameters
但是,对于一个应该是普通的概念来说,这太多的仪式和大量的工作。幸运的是,Rust有一个特性,可以在不用转移所有权的情况下使用值,它叫做引用(references)。
接下来将讲”引用和借用(References and Borrowing)”
Ch 04.00:Understanding Ownership
Ch 03.05:总结(Summary)
总结(Summary)
你做到了!这是相当大的一章:你学习了关于variables, scalar and compound data type, functions, comments, if expressions, and loops! 讨论并练习这些概念。尝试构建如下的程序:
- 相互转换摄氏与华氏温度(Convert temperatures between Fahrenheit and Celsius.)
- 生成n阶斐波那契数列(Generate th nth Fibonacci number.)
- 打印圣诞颂歌词,并利用歌词中的重复部分(编写循环)(Print the lyrics to the Christmas carol “The Twelve Days of Christmas,” taking advantage of the repetition in the song.)
当你准备好继续的时候,让我们讨论一个其他语言中不常见的概念:所有权(ownership)
Ch 03.04:注释(Comments)
注释(Comments)
所有的程序设计人员努力使他们的代码更容易理解,但是有时候额外的解释是有必要的。在这种情况下程序设计人员在源代码中留下注释(comments),编译器会忽略这些注释,但是人阅读源码会发现它很有用。
这里有一个简单的注释例子:
1 | #![allow(unused)] |
注释可以放在代码行结尾:
src/main.rs
1 | fn main() { |
但是你经常看到更多是如下的格式,在代码的上方的单独一行注释:
1 | fn main() { |
Rust 也有其他类型的注释,文档注释(documentation comments),详细在第14章的”Publishing a Crate to Crates.io” 部分介绍。
Ch 03.03:函数(Functions)
函数(Functions)
函数在Rust代码中是很普遍的。你已经看了在语言中最重要的函数之一:main函数,是很多程序的入口。你也看到了fn关键字,它允许你声明一个新的函数。
Rust代码中的函数和变量使用snake case的代码风格,所有的单词小写并用下划线隔开。这里有一个程序包含了一个函数的定义:
1 | #![allow(unused)] |
其次,five函数没有参数并定义了返回值类型,不过函数体只有单单一个5,因为它是一个表达式,可以返回我们想要的值。
让我们看看另外一个例子:
src/main.rs
1 | fn main() { |
编译这段代码,会产生一个如下的错误:
1 | $ cargo run |
主要的错误信息”mismatched types”揭示了这段代码的核心问题所在。定义函数plus_one,然后说要返回一个i32,但是语句不会计算得到一个值,使用单位类型()表示不返回值。因为不返回值与函数返回一个i32类型的值矛盾,从而出现一个错误。在输出中,Rust提供了一条信息,可能有助于纠正这个错误:它建议删除分号,这会修复这个错误。
正确的代码如下:
1 | fn main() { |
Ch 03.02:无效的数组元素访问
无效的数组元素访问
让我们看看发生了什么,如果你尝试访问数组的一个元素,这个元素在数组的结尾之后呢。运行如下的代码,类似于第二章猜数字游戏,从用户输入那里获取数组索引:
src/main.rs
1 | use std::io; |
这段代码编译成功。如果运行这段代码使用cargo run然后输入0,1,2,3,4,这个程序会打印出在数组内对应索引的值。如果你输入一个超出范围的数字,比如10,你会看到输出如:
1 | thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19 |
当你使用这个无效的值索引的时候,程序的结果是在运行时间的错误。程序退出并返回错误信息,并且没有运行最后的println!语句。当你试图用一个索引访问一个元素,Rust会检查你指定的这个索引是否会超过数组的长度。如果索引与数组长度相同或者更大,Rust就会死给你看。这个检查在运行时间,特别在这个例子中,因为编译器在编译完之后就不可能知道用户会输入什么。
这是一个Rust的内存安全原则示例的表现。在很多底层代码语言中,当你提供一个错误的索引,这种类型的检查它们是不做的,无效内存就会被访问,会导致你不知道你到底访问到了别的什么奇奇怪怪的值。Rust项目中会立即退出,而不是允许你继续访问,从而保护你面授此类错误的影响。第九章将会讨论Rust的错误处理。
Ch 03.01:隐藏(Shadowing)
隐藏(Shadowing)
如你在第二章猜数字游戏教程所见,你可以声明一个新的变量用和前面声明的变量同样的名字。Rustaceans说第一个变量被第二个隐藏(Shadowing)了,意思是第二个变量的值是在程序使用时才看到的。我们可以shadow一个变量使用相同的变量名,并重复地使用let关键字,如下:
src/main.rs
1 | fn main() { |
程序首先绑定了5给x。然后重新用let x=隐藏了x,使得原来的值加1,所以变量的值变为了6。然后,在内部作用域,第三次let声明x, 将之前的值乘以2得到12。当内部作用结束,内部的隐藏结束,并且x变回6。当我们运行这个程序,它运行结果如下:
1 | $ cargo run |
隐藏不同于将变量标记为mut,因为如果我们意外地尝试重新分配这个变量不使用let关键字,我们会得到一个编译错误。通过使用let,我们可以对一个值执行一些转换,但在这些转换完成后,变量是不可变的。
其他的mut和shadowing之间的不同之处是因为当我们再次使用let关键字,我们实际上创建了一个新的变量。例如,假如我们的程序要求用户输入空格来显示文本之间需要多少空格,然后我们希望将输入存储为数字:
1 | fn main() { |
首先spaces变量是一个string类型,然后第二个spaces变量是数字类型。因此shadowing使我们不需使用不同的变量名,如space_str 和 space_num;然而,如果我们尝试使用mut,如下,我们会得到一个编译时错误:
1 | fn main() { |
错误说我们不被允许转变变量的类型:
1 | $ cargo run |
现在,我们探索了变量是如何工作的,我们还有更多的数据类型等着你呢!