是不同类型值的集合。元组是使用括号构造(),且每个元组本身是具有式签名的值 (T1, T2, …),其中T1,T2是所述类型的成员。函数可以使用元组返回多个值,因为元组可以保存任意数量的值。
是T存储在连续内存中的相同类型对象的集合。数组是使用方括号创建的[],它们的长度(在编译时已知)是它们类型签名的一部分[T; length]。
切片类似于数组,但它们的长度在编译时是未知的。相反,切片是一个双字对象,第一个字是指向数据的指针,第二个字是切片的长度。字长与 usize 相同,由处理器架构决定,例如 x86-64 上的 64 位。切片可用于借用数组的一部分,并具有类型签名 &[T]。
枚举
常数
默认情况下,变量绑定是不可变的,但这可以使用mut修饰符覆盖。
变量绑定有一个作用域,并且被限制在一个块中。块是用大括号括起来的一组语句{}。
可以先声明变量绑定,然后再初始化它们。但是,这种形式很少使用,因为它可能导致使用未初始化的变量。
当数据以不变的名称绑定在一起时,它也会冻结。在不可变绑定超出范围之前,无法修改冻结的数据:
fn main() {
let mut _mutable_integer = 7i32;
{
// Shadowing by immutable `_mutable_integer`
let _mutable_integer = _mutable_integer;
// Error! `_mutable_integer` is frozen in this scope
_mutable_integer = 50;
// FIXME ^ Comment out this line
// `_mutable_integer` goes out of scope
}
// Ok! `_mutable_integer` is not frozen in this scope
_mutable_integer = 3;
}
Rust 不提供原始类型之间的隐式类型转换(强制)。但是,可以使用as关键字执行显式类型转换(强制转换)。
整数类型之间的转换规则通常遵循 C 约定,除非 C 具有未定义的行为。整型之间的所有类型转换的行为在 Rust 中都有很好的定义。
数字文字可以通过添加类型作为后缀来进行类型注释。例如,要指定文字42应具有 type i32,请写入42i32。
无后缀数字文字的类型取决于它们的使用方式。如果不存在约束,编译器将i32用于整数和f64浮点数。
fn main() {
// Suffixed literals, their types are known at initialization
let x = 1u8;
let y = 2u32;
let z = 3f32;
// Unsuffixed literals, their types depend on how they are used
let i = 1;
let f = 1.0;
// `size_of_val` returns the size of a variable in bytes
println!("size of `x` in bytes: {}", std::mem::size_of_val(&x));
println!("size of `y` in bytes: {}", std::mem::size_of_val(&y));
println!("size of `z` in bytes: {}", std::mem::size_of_val(&z));
println!("size of `i` in bytes: {}", std::mem::size_of_val(&i));
println!("size of `f` in bytes: {}", std::mem::size_of_val(&f));
}
前面代码中用到的一些概念还没有解释,这里给不耐烦的读者简单解释一下:
std::mem::size_of_val是一个函数,但以其完整路径调用。代码可以分成称为模块的逻辑单元。在这种情况下, size_of_val函数定义在mem模块中,mem模块定义在std crate 中。有关更多详细信息,请参阅 模块和板条箱。
类型推理引擎非常聪明。它不仅仅是在初始化期间查看值表达式的类型。它还查看之后如何使用变量来推断其类型。这是类型推断的高级示例:
fn main() {
// Because of the annotation, the compiler knows that `elem` has type u8.
let elem = 5u8;
// Create an empty vector (a growable array).
let mut vec = Vec::new();
// At this point the compiler doesn't know the exact type of `vec`, it
// just knows that it's a vector of something (`Vec<_>`).
// Insert `elem` in the vector.
vec.push(elem);
// Aha! Now the compiler knows that `vec` is a vector of `u8`s (`Vec`)
// TODO ^ Try commenting out the `vec.push(elem)` line
println!("{:?}", vec);
}
不需要变量的类型注释,编译器很高兴,程序员也很高兴!
该type语句可用于为现有类型赋予新名称。类型必须有UpperCamelCase名称,否则编译器会发出警告。唯一例外的规则是基本类型:usize,f32,等。
用if-分支else类似于其他语言。与它们中的许多不同,布尔条件不需要用括号括起来,每个条件后跟一个块。if-else条件是表达式,并且所有分支必须返回相同的类型。
Rust 提供了一个loop关键字来表示无限循环。
该break语句可用于随时退出循环,而该 continue语句可用于跳过剩余的迭代并开始新的迭代。
该for in构造可用于遍历Iterator. 创建迭代器的最简单方法之一是使用范围表示法a…b。这会 以 1 的步长产生从a(包含)到b(不包含)的值。或者,a…=b可用于包含两端的范围.
fn main() {
// `n` will take the values: 1, 2, ..., 100 in each iteration
for n in 1..101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
}
}
fn main() {
// `n` will take the values: 1, 2, ..., 100 in each iteration
for n in 1..=100 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
}
}
该for in构造能够以Iterator多种方式与 交互。正如Iterator trait部分所讨论的,默认情况下,for 循环会将into_iter函数应用于集合。但是,这并不是将集合转换为迭代器的唯一方法。
into_iter,iter并且iter_mut都通过提供对其中数据的不同视图,以不同的方式处理集合到迭代器的转换。
iter- 这通过每次迭代借用集合的每个元素。从而使集合保持不变并可在循环后重复使用。
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in names.iter() {
match name {
&"Ferris" => println!("There is a rustacean among us!"),
// TODO ^ Try deleting the & and matching just "Ferris"
_ => println!("Hello {}", name),
}
}
println!("names: {:?}", names);
}
into_iter- 这会消耗集合,以便在每次迭代时提供准确的数据。一旦集合被消耗,它就不再可用于重用,因为它已在循环中“移动”。
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in names.into_iter() {
match name {
"Ferris" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
println!("names: {:?}", names);
// FIXME ^ Comment out this line
}
iter_mut - 这会可变地借用集合的每个元素,允许就地修改集合。
fn main() {
let mut names = vec!["Bob", "Frank", "Ferris"];
for name in names.iter_mut() {
*name = match name {
&mut "Ferris" => "There is a rustacean among us!",
_ => "Hello",
}
}
println!("names: {:?}", names);
}
Rust 通过match关键字提供模式匹配,它可以像 C 一样使用switch。评估第一个匹配臂,并且必须涵盖所有可能的值。
结构元组,结构体
对于指针,需要区分解构和解引用,因为它们是不同的概念,与C.
取消引用用途 *
解构使用&, ref, 和ref mut
结构
卫兵:编译器不会检查任意表达式是否已检查所有可能的条件。因此,您必须_在最后使用模式。
捆绑:
if let
while let
方法是附加到对象的函数。这些方法可以通过self关键字访问对象及其其他方法的数据。方法在impl块下定义。
闭包是可以捕获封闭环境的函数。例如,一个捕获 x 变量的闭包:
|val| val + x
闭包的语法和功能使其非常便于即时使用。调用闭包就像调用函数一样。但是,可以推断输入和返回类型,并且必须指定输入变量名称。
闭包的其他特征包括:
使用||而不是()围绕输入变量。
{}单个表达式的可选正文分隔 ( )(否则为强制性)。
捕获外部环境变量的能力。
默认情况下,模块中的项目具有私有可见性,但这可以用pub修饰符覆盖。只有模块的公共项可以从模块范围之外访问。
结构对其字段具有额外的可见性。可见性默认为私有,并且可以用pub修饰符覆盖。这种可见性仅在从定义它的模块外部访问结构时才重要,并且具有隐藏信息(封装)的目标。
该use声明可用于将完整路径绑定到新名称,以便于访问。
的super和self关键字可以在路径中使用访问的项目时,以除去模糊并防止不必要的路径硬编码。
模块可以映射到文件/目录层次结构。让我们分解文件中的 可见性示例:
依赖关系
公约
测试
构建脚本
该crate_type属性可用于告诉编译器一个 crate 是二进制文件还是库(甚至是哪种类型的库),该crate_name 属性可用于设置 crate 的名称。
某些条件,例如target_os隐式提供rustc,但自定义条件必须传递给rustc使用–cfg标志。
作用域在所有权、借用和生命周期中扮演着重要的角色。也就是说,它们向编译器指示何时借用有效、何时可以释放资源以及何时创建或销毁变量。
Rust 中的变量不仅仅在堆栈中保存数据:它们还拥有 资源,例如Box拥有堆中的内存。Rust 强制执行RAII (资源获取即初始化),因此只要对象超出范围,就会调用其析构函数并释放其拥有的资源。
此行为可防止资源泄漏错误,因此您永远不必手动释放内存或再次担心内存泄漏!
因为变量负责释放自己的资源, 资源只能有一个 owner。这也可以防止资源被多次释放。请注意,并非所有变量都拥有资源(例如引用)。
在进行赋值 ( let x = y) 或按值 ( foo(x))传递函数参数时,资源的所有权将被转移。在 Rust 中,这被称为移动。
移动资源后,不能再使用以前的所有者。这避免了创建悬空指针。
当所有权转移时,数据的可变性可以改变。
在单个变量的解构中,可以同时使用by-move和 by-reference模式绑定。这样做将导致变量的部分移动,这意味着变量的一部分将被移动,而其他部分将保持不变。在这种情况下,父变量不能作为一个整体使用,但是仍然可以使用仅引用(而不是移动)的部分。
大多数情况下,我们希望在不拥有所有权的情况下访问数据。为了实现这一点,Rust 使用了借用机制。T可以通过引用 ( &T)传递对象,而不是按值 ( )传递对象。
编译器静态地保证(通过其借用检查器)引用 始终指向有效对象。也就是说,虽然存在对对象的引用,但该对象不能被销毁。
寿命是一个构建体中的编译器(或更具体地,其借检查器)使用,以确保所有借位是有效的。具体来说,变量的生命周期从创建时开始,到销毁时结束。虽然生命周期和作用域经常一起被引用,但它们并不相同。
以我们通过 借用变量的情况为例&。借用的生命周期由它的声明位置决定。因此,只要在贷方被销毁之前结束,借入就是有效的。但是,借用的范围取决于引用的使用位置。
宏的参数以美元符号为前缀$,类型用指示符注释:
我们将看到的最简单的错误处理机制是panic. 它会打印一条错误消息,开始展开堆栈,然后通常会退出程序。在这里,我们明确调用panic我们的错误条件: