在 Rust 中,将一个值赋给一个名字称为 绑定。除非使用mut关键字声明,否则绑定是不可变的。由于 Rust 是一种静态类型语言,每个绑定都有一个在编译时已知的类型。
绑定最常使用let关键字定义。对于大多数绑定,指定绑定的类型是可选的,因为 Rust 的 类型推断 通常可以根据它们的值推断出类型。一个绑定看起来像这样:
// Automatically inferred type
let fingers = 10;
函数是 项。绑定通常指向一个特定的值,而项则指向一个代码组织单元,通常是一个函数或一个模块,它在程序的整个生命周期内都可用。函数自动返回其最后一个表达式的结果。函数可以有 0 个或多个形参,它们是生命周期为函数调用的绑定。
类型推断在理论上对函数也是可能的,但作为有意的语言设计选择而被禁用。这意味着你在编写代码时需要多花一点时间,精确指定函数的输入和输出类型,但你在阅读代码时可以节省时间,因为所有的输入和输出类型都被显式定义了。
fn add(x: i32, y: i32) -> i32 {
x + y
}
调用函数的方法是写出它的名字,后面加上括号。如果函数需要形参,则必须在括号内为每个形参指定一个实参。
let five = add(2, 3);
如果无法推断绑定的类型,编译器将报告一个错误。要解决这个问题,请为绑定添加显式的类型标注。
// Explicit type annotation
let fingers: i32 = 10;
Rust 中的项可以在定义之前或之后使用,因为它们具有静态生命周期。另一方面,绑定只能在定义 之后 使用。在定义之前使用绑定会导致编译错误。
fn main() {
// `fn add` hasn't yet been defined, but that's perfectly ok
dbg!(add(3, 4));
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
// this won't compile; `a` is used before its binding is defined
let b = a;
let a = x + y;
Rust 使用大括号({})来定义作用域。在作用域内定义的绑定无法从中逃脱。
let a = 1;
dbg!(a); // 1
{
// Here, we re-bind `a` to a new value, which is still immutable.
// This technique is called _shadowing_. The new binding is constrained to
// this anonymous scope. Outside this scope, the previous binding still
// applies.
let a = 2;
let b = 3;
dbg!(a, b); // 2, 3
}
// can't use `b` anymore because it is out of scope
// dbg!(b);
// The shadowed `a` in the inner scope above has fallen out of scope,
// leaving us with our original binding.
dbg!(a); // 1
Rust 的项通常组织在模块中。每个包隐式地是一个模块,但它可以定义任意深度的内部子模块。模块将相关的功能分组在一起,并使用mod关键字定义。
mod calc_i32 {
fn add(a: i32, b: i32) -> i32 { a + b }
fn sub(a: i32, b: i32) -> i32 { a - b }
fn mul(a: i32, b: i32) -> i32 { a * b }
fn div(a: i32, b: i32) -> i32 { a / b }
}
Rust 支持两种类型的注释。//关键字表示单行注释;从该关键字开始直到行尾的所有内容都会被忽略。/*和*/关键字表示多行注释;这两个关键字之间的所有内容都会被忽略。优先使用单行注释是符合习惯且良好的做法。
Rust 还支持文档注释,它们会出现在cargo doc生成的文档中。外部文档注释由关键字///构成,其作用与//关键字完全相同。它们应用于紧随其后的项,例如一个函数:
/// The `add` function produces the sum of its arguments.
fn add(x: i32, y: i32) -> i32 { x + y }
内部文档注释由关键字//!构成,其作用与//关键字完全相同。它们应用于包含它们的项,例如一个模块:
mod my_cool_module {
//! This module is the bee's knees.
}
文档注释可以任意长度,并且可以包含 Markdown,Markdown 会被渲染到生成的文档中。
在这个练习中,你将编写一些代码,帮助你按照最喜欢的烹饪书做出一份美味的千层面。
你有四个任务,它们都与烹饪千层面所花的时间有关。
定义 expected_minutes_in_oven 绑定,用来表示千层面应该在烤箱里烤多少分钟。根据烹饪书,预期的烤箱时间是 40 分钟:
expected_minutes_in_oven()
// Returns: 40
定义 remaining_minutes_in_oven 函数,它接受千层面已在烤箱中烤过的实际分钟数作为形参,并根据上一个任务中预期的烤箱时间,返回千层面还需要在烤箱中烤多少分钟。
remaining_minutes_in_oven(30)
// Returns: 10
定义 preparation_time_in_minutes 函数,它接受你为千层面添加的层数作为形参,并返回你准备千层面所花的分钟数,假设准备每层需要 2 分钟。
preparation_time_in_minutes(2)
// Returns: 4
定义 elapsed_time_in_minutes 函数,它接受两个形参:第一个形参是你为千层面添加的层数,第二个形参是千层面已在烤箱中烤过的分钟数。该函数应返回你在烹饪千层面上花掉的分钟数,也就是准备时间的分钟数与此刻千层面已在烤箱中烤过的分钟数之和。
elapsed_time_in_minutes(3, 20)
// Returns: 26