一个人为了更好地掌握一门编程语言,不得不去进行大量的阅读。但是如果你都不知道代码的意思,又怎么能进行大量阅读呢?
在本文中,我将尽我所能去剖析尽可能多的Rust代码片段并解释其中包含的符号和关键字的含义。
准备好了吗?Go!
let 关键字表示一个变量绑定(binding)
let x; // 声明 "x"
x = 42; // 把42赋值给"x"
上面的代码也可以写成一行:
let x = 42;
你可以用 :
显示指定变量类型,即类型标注(type annotation):
let x: i32; // `i32` 是一个有符号的32位(32-bit)整数
x = 42;
// 还有 i8, i16, i32, i64, i128
// 以及无符号的 u8, u16, u32, u64, u128
上面的代码也可以写成一行:
let x: i32 = 42;
如果你声明了一个变量但是要在后面对它进行初始化,编译器就会阻止你在这个变量初始化之前使用它。
let x;
foobar(x); // 错误: 借用一个可能没有初始化的变量: `x`
x = 42;
但是,下面这样写就没有问题了:
let x;
x = 42;
foobar(x); // `x` 的类型会从这里推导出来
下划线_
是一种特殊的变量,更准确的说叫"缺省变量"。这表示丢弃某些东西:
// 因为42是常量,这里什么也不做
let _ = 42;
// 这里调用`get_thing`但是丢弃了它的结果
let _ = get_thing();
以下划线开头的变量名也是正常的变量名,只是告诉编译器在这么变量未被使用的情况下也不要警告:
//我们可能最终会用到`_x`,但是我们的工作还没完成,这个时候我们只是想要避免编译期的警告
let _x = 42;
相同的变量名也可以进行不同的绑定-你可以遮盖(shadow)一个变量绑定:
let x = 13;
let x = x + 3;
// 在第二行之后使用的`x`都指向第二个`x`,
// 第一个 `x` 就不存在了。
Rust里有元组(tuple),元组你可以认为是一种具有固定的长度的集合,集合里面的值可以是不同类型的。
let pair = ('a', 17);
pair.0; // 这个值 'a'
pair.1; // 这个值 17
如果我们真的想标注***pair***的类型,我们可以这样写:
let pair: (char, i32) = ('a', 17);
元组(tuple)在赋值的时候可以被拆分,意思是指元组可以被拆分成独立的字段:
let (some_char, some_int) = ('a', 17);
// 现在, `some_char` 的值是 'a', `some_int` 的值是 17
当函数函数返回一个元组的时候,这是非常有用的:
let (left, right) = slice.split_at(middle);
当然,在拆分元组的时候,_
可以用于丢弃其中的一部分:
let (_, right) = slice.split_at(middle);
分号表示声明(statement)语句的结束:
let x = 3;
let y = 5;
let z = y + x;
这也意味着声明(statement)语句可以跨越很多行:
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
(我们后面再解释这些代码的含义)
fn
声明一个函数。
这是一个没有返回值的函数:
fn greet() {
println!("Hi there!");
}
下面是一个返回值为32位有符号整数的函数。箭头表示它的返回值类型:
fn fair_dice_roll() -> i32 {
4
}
一对大括号声明了一个代码块,代码块有自己的作用域(own scope)
// 下面的代码会先输出 "in", 然后输出 "out"
fn main() {
let x = "out";
{
// 这是另一个不同的 `x`
let x = "in";
println!(x);
}
println!(x);
}
块(block)也是表达式,这意味着块(block) 可以作为值:
// 这个:
let x = 42;
// 等价于下面:
let x = { 42 };
在块里面,可以有多个声明(statements) :
let x = {
let y = 1; // 第一个声明
let z = 2; // 第二个声明
y + z // 这是结尾 - 整个块返回的最终结果
};
这就是为什么说"函数结尾处省略分号"和返回结果(returning)是一样的。比如下面的代码就是等价的:
fn fair_dice_roll() -> i32 {
return 4;
}
fn fair_dice_roll() -> i32 {
4
}
if
条件判断也是表达式:
fn fair_dice_roll() -> i32 {
if feeling_lucky {
6
} else {
4
}
}
一个match
操作也是一个表达式:
fn fair_dice_roll() -> i32 {
match feeling_lucky {
true => 6,
false => 4,
}
}
点(.)操作符经常用于访问一个值里面的字段:
let a = (10, 20);
a.0; // this is 10
let amos = get_some_struct();
amos.nickname; // this is "fasterthanlime"
或者用于调用一个值的方法:
let nick = "fasterthanlime";
nick.len(); // this is 14
双冒号::
和上面相似,但是用于命名空间。
在这个例子里,std
是一个crate(~类似一个库), cmp
是一个模块(~类似一个源文件),min
是一个函数:
let least = std::cmp::min(3, 8); //least的值是 3
use
指令可以用于把其他命名空间里的变量带过来(bring in scope):
use std::cmp::min;
let least = min(7, 1); // this is 1
在use
指令中,大括号有另一个意思:它们是"一团"(globs)。如果我们想要一起倒入min
和max
,我们可以这样写:
// 这样写可以:
use std::cmp::min;
use std::cmp::max;
// 这样写也可以:
use std::cmp::{min, max};
// 这样写也可以:
use std::{cmp::min, cmp::max};
使用一个通配符(*
)可以导入一个命名空间内的所有符号(symbol):
// 不仅导入了 `min` and `max` ,还导入了其他很多东西。
use std::cmp::*;
str
是一种原始类型,但是很多非原始类型默认也被引入了:
// `Vec` 是一个普通的结构体, 不是一个原始类型
let v = Vec::new();
// 这是和上面相同的代码,但是带上了Vec的完整路径
let v = std::vec::Vec::new();
上面的代码可以工作是因为Rust在每个模块(module)的开头插入了下面的代码:
use std::prelude::v1::*;
(上面这些代码重新导入了很多符号,像Vec
,String
,Option
和Result
)。
声明结构体使用struct
关键字:
struct Vec2 {
x: f64, // 64位浮点数, 也叫 "双精度"
y: f64,
}
它们可以用结构体字面量(struct literals)来初始化:
let v1=Vec2{x:1.0, y:3.0};
let v2=Vec2{y:2.0, x:4.0};
//顺序不重要,只要名字可以对应上就可以
从另一个结构初始化剩余的字段可以使用缩写的形式:
let v3 = Vec2 {
x: 14.0,
..v2
};
这叫做"结构体更新语法(struct update syntax)",而且只能写在结构体最后的位置并且后面不能有逗号。
注意,剩余的字段可以指所有的字段:
let v4 = Vec2 { ..v3 };
结构体和元组一样,也可以被拆分。
例如下面这样子,就是一个有效的let
模式:
let (left, right) = slice.split_at(middle);
下面这个也是:
let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` 现在是 3.0, `y` 现在是 `6.0`
以及下面这个:
let Vec2 { x, .. } = v;
// 这里丢弃了 v.y
let
模式可以在if
语句中用作条件判断(conditions):
struct Number {
odd: bool,
value: i32,
}
fn main() {
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
print_number(one);
print_number(two);
}
fn print_number(n: Number) {
if let Number { odd: true, value } = n {
println!("Odd number: {}", value);
} else if let Number { odd: false, value } = n {
println!("Even number: {}", value);
}
}
// 上面的代码输出如下:
// Odd number: 1
// Even number: 2
match
匹配也是模式(patterns),就想if let
:
fn print_number(n: Number) {
match n {
Number { odd: true, value } => println!("Odd number: {}", value),
Number { odd: false, value } => println!("Even number: {}", value),
}
}
// 这段代码的输出和上一次的一样
一个match
匹配必须是完备的:至少有一个分支能够匹配上(译者注:这里作者的意思应该是要详尽地列出所有的可能性,才能保证至少能匹配到其中的一项)。
fn print_number(n: Number) {
match n {
Number { value: 1, .. } => println!("One"),
Number { value: 2, .. } => println!("Two"),
Number { value, .. } => println!("{}", value),
// if that last arm didn't exist, we would get a compile-time error
}
}
如果很难列出所有的情况,可以使用_
作为"捕获全部"的模式:
fn print_number(n: Number) {
match n.value {
1 => println!("One"),
2 => println!("Two"),
_ => println!("{}", n.value),
}
}
你可以为自己定义的类型声明方法:
struct Number {
odd: bool,
value: i32,
}
impl Number {
fn is_strictly_positive(self) -> bool {
self.value > 0
}
}
然后就可以像平常一样使用它们
fn main() {
let minus_two = Number {
odd: false,
value: -2,
};
println!("positive? {}", minus_two.is_strictly_positive());
// 这段代码会打印 "positive? false"
}
变量绑定默认是不可变的(immutable):
fn main() {
let n = Number {
odd: true,
value: 17,
};
n.odd = false; // 错误: 不能对`n.odd`赋值因为`n`没有声明为可变的
}
一个不可变的变量绑定不能修改其内部的信息(就行我们刚刚尝试的),并且它自身也不能被赋值:
fn main() {
let n = Number {
odd: true,
value: 17,
};
n = Number {
odd: false,
value: 22,
}; // 错误: 不能对不可变变量`n`赋值两次
}
mut
让一个变量绑定可以被修改(mutable):
fn main() {
let mut n = Number {
odd: true,
value: 17,
}
n.value = 19; // all good
}
Trait是可以被多个类型共同拥有(译者注:这里trait不进行翻译,因为看到一些书籍里保留了原词):
trait Signed {
fn is_strictly_negative(self) -> bool;
}
你可以实现:
- 在任何人的类型上实现你的trait
- 在你定义的类型上实现任何人的trait
- 但是不能给外部的类型实现外部的trait
以上被称为"孤儿原则"。
下面是在我们定义的类型上实现我们定义的trait:
impl Signed for Number {
fn is_strictly_negative(self) -> bool {
self.value < 0
}
}
fn main() {
let n = Number { odd: false, value: -44 };
println!("{}", n.is_strictly_negative()); // prints "true"
}
在外部类型上实现我们定义的trait(甚至可以是一个原始类型):
impl Signed for i32 {
fn is_strictly_negative(self) -> bool {
self < 0
}
}
fn main() {
let n: i32 = -44;
println!("{}", n.is_strictly_negative()); // prints "true"
}
在我们的类型上实现一个外部的trait:
// the `Neg` trait用于重载减号`-`, 也就是一元减法操作符
impl std::ops::Neg for Number {
type Output = Number;
fn neg(self) -> Number {
Number {
value: -self.value,
odd: self.odd,
}
}
}
fn main() {
let n = Number { odd: true, value: 987 };
let m = -n; // 因为我们实现了`Neg` trait,这里才是行得通的。
println!("{}", m.value); // 打印输出: "-987"
}
一个impl
块总是针对一个类型,所以,在这个块里,Self
表示那个类型:
impl std::ops::Neg for Number {
type Output = Self;
fn neg(self) -> Self {
Self {
value: -self.value,
odd: self.odd,
}
}
}
一些traits是起标识作用的-它们不说一个类型实现了某些方法,他们说一些事情只能被某个类型完成。
例如,i32
实现了Copy
trait(简而言之,就是i32
是Copy
),因此,下面的代码是有效的:
fn main() {
let a: i32 = 15;
let b = a; // `a` 被拷贝
let c = a; // `a` 再次被拷贝
}
下面的代码也是行得通的:
fn print_i32(x: i32) {
println!("x = {}", x);
}
fn main() {
let a: i32 = 15;
print_i32(a); // `a` 被拷贝
print_i32(a); // `a` 再次被拷贝
}
但是Number
结构体不是Copy
, 所以下面的代码行不通:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `n` 被移动到 `m`
let o = n; // 错误: 使用以及被移动过的值: `n`
}
但是如果print_number
使用一个不可变引用作为参数就可以行得通:
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(&n); // `n` is borrowed for the time of the call
print_number(&n); // `n` is borrowed again
}
这样子也是行得通的:如果一个函数使用一个可变引用(mutable reference)作为参数并且我们的变量绑定也是mut
的时候。
fn invert(n: &mut Number) {
n.value = -n.value;
}
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
// 这次, `n` 是可变的
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n); // `n`被可变借用 - 一切都是显式的
print_number(&n);
}
Trait方法也可以使用self
的引用(默认不可变)或者可变引用:
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
当执行trait方法时,接收者(receiver)是隐式借用:
fn main() {
let n = Number { odd: true, value: 51 };
let mut m = n.clone();
m.value += 100;
print_number(&n);
print_number(&m);
}
强调一下,下面是等价的:
let m = n.clone();
let m = std::clone::Clone::clone(&n);
标识traits像Copy
这种事没有方法的:
// note: `Copy` requires that `Clone` is implemented too
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
impl std::marker::Copy for Number {}
现在,Clone
仍然可以被使用:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n.clone();
let o = n.clone();
}
但是Number
的值将不会被移动:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `m` is a copy of `n`
let o = n; // same. `n` is neither moved nor borrowed.
}
一些traits会经常被用到,它们可以通过使用derive
属性自动被实现:
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// this expands to `impl Clone for Number` and `impl Copy for Number` blocks.
函数可以是泛型的(generic):
fn foobar<T>(arg: T) {
// do something with `arg`
}
它们可以有多个类型参数而不是具体的类型用于函数的声明和函数体:
fn foobar<L, R>(left: L, right: R) {
// do something with `left` and `right`
}
类型参数通常会有约束,所以你可以用这些约束来做一些事情。
最简单的约束就是trait变量名:
fn print<T: Display>(value: T) {
println!("value = {}", value);
}
fn print<T: Debug>(value: T) {
println!("value = {:?}", value);
}
类型参数约束还有一种更长一点儿的语法:
fn print<T>(value: T)
where
T: Display,
{
println!("value = {}", value);
}
约束可以更复杂:它们可以要求一个类型参数实现多个trait:
use std::fmt::Debug;
fn compare<T>(left: T, right: T)
where
T: Debug + PartialEq,
{
println!("{:?} {} {:?}", left, if left == right { "==" } else { "!=" }, right);
}
fn main() {
compare("tea", "coffee");
// prints: "tea" != "coffee"
}
泛型函数可以被认为是命名空间,包含了无数的具体类型的函数。
类似于crates和modules以及types,泛型函数也可以使用::
来探索(explored、navigated?)(译者注:这里不知道该怎么翻译,应该是说可以使用::
符号来引用里面针对具体类型的函数):
fn main() {
use std::any::type_name;
println!("{}", type_name::<i32>()); // prints "i32"
println!("{}", type_name::<(f64, char)>()); // prints "(f64, char)"
}
这种方式被亲切地称为turbofish语法(turbofish syntax),因为::<>看起来像一条鱼。
结构体也可以是泛型的:
struct Pair<T> {
a: T,
b: T,
}
fn print_type_name<T>(_val: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let p1 = Pair { a: 3, b: 9 };
let p2 = Pair { a: true, b: false };
print_type_name(&p1); // prints "Pair<i32>"
print_type_name(&p2); // prints "Pair<bool>"
}
标准库里的Vec
(一个分配在堆上的数组),就是泛型的:
fn main() {
let mut v1 = Vec::new();
v1.push(1);
let mut v2 = Vec::new();
v2.push(false);
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
提及Vec
,它有一个能多少给出一些"vec literals"的宏(译者注:由于译者水平有限,这里的vec literals暂时不知道如何翻译,敬请谅解):
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
所有的形如name!()
,name![]
,或者name!{}
都执行一个宏。宏只是展开成正常的代码而已。
事实上,println
也是一个宏:
fn main() {
println!("{}", "Hello there!");
}
上面代码展开成后,和下面的代码具有相同的作用:
fn main() {
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}
panic
也是一个宏.它很暴力的停止程序的执行,带有错误信息以及出错的文件名/行号,如果开启这些选项的话:
fn main() {
panic!("This panics");
}
// output: thread 'main' panicked at 'This panics', src/main.rs:3:5
后续: