半小时学Rust
文章翻译自英文博客,内容有删减。在此感谢原作者分享的精神!
原文地址:A half-hour to learn Rust
原文作者:amos loves to tinker
说明:文章将根据个人理解深度,将不定期修改编辑,也欢迎同学提出宝贵建议!
有时为了加强编程语言的熟练程度,我们需要阅读很多相关文档。但如果不知道什么意思你如何去阅读呢?
在本文中,我将介绍尽可能多的Rust Snippets,而不去过多关注某几个概念,而且会解释他们所包含关键字和符号的意义。
准备好了吗?那开始吧!
变量绑定
let
介绍了一种变量绑定的方式:
let x; // declare "x"
x = 42; // assign 42 to "x"
同样可以将生命变量和指定值写成一行:
let x = 42;
你也可以用类型注解符:
显示地指定变量类型
let x: i32; // `i32` is a signed 32-bit integer
x = 42;
// there's i8, i16, i32, i64, i128
// also u8, u16, u32, u64, u128 for unsigned
当然,也可以写成一行:
let x: i32 = 42;
如果你声明了一个变量,但并没有马上对其进行初始化,那么编译器将会阻止你在对其进行初始化前使用它:
let x;
foobar(x); // error: borrow of possibly-uninitialized variable: `x`
x = 42;
然而,这样做却是完全可以的:
let x;
x = 42;
foobar(x); // the type of `x` will be inferred from here
下划线是一个特殊的变量名称,或者缺少名称,基本意味着扔掉一些内容:
// this does *nothing* because 42 is a constant
let _ = 42;
// this calls `get_thing` but throws away its result
let _ = get_thing();
以下划线开始的变量名称是regular名称,编译器在该名称未使用时不会给出警告信息:
// we may use `_x` eventually, but our code is a work-in-progress
// and we just wanted to get rid of a compiler warning for now.
let _x = 42;
允许分别绑定同一名称的变量,这样会隐藏一个变量绑定:
let x = 13;
let x = x + 3;
// using `x` after that line only refers to the second `x`,
// the first `x` no longer exists.
Rust拥有元组类型,可把它看成固定长度的不同类型值的集合
:
let pair = ('a', 17);
pair.0; // this is 'a'
pair.1; // this is 17
假如我们特别想注解pair
的类型,我们可以这么写:
let pair: (char, i32) = ('a', 17);
元组可以在赋值时被解构,即元组中的元素被分解到各自独立的域中:
let (some_char, some_int) = ('a', 17);
// now, `some_char` is 'a', and `some_int` is 17
这在一个函数返回一个元组时相当有用:
let (left, right) = slice.split_at(middle);
当然,当解构一个元组时, 下划线_
可以用来充当不需要显示的值:
let (_, right) = slice.split_at(middle);
语句与表达式
分号标志着语句的结束:
let x = 3;
let y = 5;
let z = y + x;
也就是说一个语句可以衍生(span)多行:
let x = vec![1,2,3,4,5,6,7,8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
(待会解释该代码段什么意思!)
函数
fn 声明一个函数。
(1) 无返回值的函数:
fn greet() {
println!("Hi, there!");
}
(2) 返回一个i32整数值的函数,箭头->
表示其返回值类型:
fn fair_dice_roll() -> i32 {
4
}
一对大括号代表一个代码块, 拥有自己的域:
// This prints "in", then "out"
fn main() {
let x = "out";
{
// this is a different `x`
let x = "in";
println!(x);
}
println!(x);
}
代码块也是表达式,也就是说代码块计算出的值给一个值
// this:
let x = 42;
// is equivalent to this:
let x = { 42 };
在一个代码块中,一般会有多个语句:
let x = {
let y = 1; // first statement
let z = 2; // second statement
y + z // this is the *tail* - what the whole block will evaluate to
};
也就是说为什么省略函数最后的大括号跟返回值具有一样的效果,即等价关系如下:
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(34, 90); // this is 34
use指令可用于将其他名称空间中的名称“引入范围”:
use std::cmp::min;
let least = min(7, 1); // this is 1
在use指令中,花括号的另一含义是:“globs”。假如我们要同时引用min
和max
,我们可以做如下操作:
// this works:
use std::cmp::min;
use std::cmp::max;
// this also works:
use std::cmp::{min, max};
// this also works!
use std::{cmp::min, cmp::max};
通配符*
允许你从一个命名空间中引入每个symbol:
// this brings `min` and `max` in scope, and many other things
use std::cmp::*;
类型也是命名空间,方法可以像常规函数一样被调用:
let x = "amos".len(); // this is 4
let x = str::len("amos"); // this is also 4
str
是一个原始类型,但是很多非原始类型也在默认的范围内。
// `Vec` is a regular struct, not a primitive type
let v = Vec::new();
// this is exactly the same code, but with the *full* path to `Vec`
let v = std::vec::Vec::new();
如下引用也是起作用的,因为Rust将在每个模块的开始插入如下引用:
use std::prelude::v1::*;
(反过来又重新导出了很多符号,例如Vec,String,Option和Result)
关键字struct
用于结构体声明:
struct Vec2 {
x: f64, // 64-bit floating point, aka "double precision"
y: f64,
}
可以使用结构文字初始化它们:
let v1 = Vec2 { x: 1.0, y: 3.0 };
let v2 = Vec2 { y: 2.0, x: 4.0 };
// the order does not matter, only the names do
可以使用缩略形式初始化剩余的来自于其他结构体的域:
let v3 = Vec2 {
x: 14.0,
..v2
};
这种形式被称作“结构体更新语法”,只能发生在结构体的最后一个位置,且不能跟逗号.
请注意,其余字段可以表示所有字段:
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` is now 3.0, `y` is now `6.0`
还有这个:
let Vec2 { x, .. } = v;
// this throws away `v.y`
let
模式可以被用于if
的条件:
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);
}
}
// this prints:
// Odd number: 1
// Even number: 2
match
操作同样也是模式,像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),
}
}
// this prints the same as before
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
}
}
若列出所有匹配分支是比较困难的,那么下划线_
可以用作“catch-all”模式:
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());
// this prints "positive? false"
}
变量绑定默认是不可变的:
fn main() {
let n = Number {
odd: true,
value: 17,
};
n.odd = false; // error: cannot assign to `n.odd`,
// as `n` is not declared to be mutable
}
不可变变量绑定不能对其内部进行更改(就像我们刚才尝试的那样),但是也不能将其分配
fn main() {
let n = Number {
odd: true,
value: 17,
};
n = Number {
odd: false,
value: 22,
}; // error: cannot assign twice to immutable variable `n`
}
mut
关键字使得变量为可变绑定:
fn main() {
let mut n = Number {
odd: true,
value: 17,
}
n.value = 19; // all good
}
Traits
Trait 是多种类型共同拥有的一种性质:
trait Signed {
fn is_strictly_negative(self) -> bool;
}
你可以实现为:
(1)如果要实现外部定义的 trait 需要先将其导入作用域。
(2)不允许对外部类型实现外部 trait;
(3)可以对外部类型实现自定义的 trait;
(4)可以对自定义类型上实现外部 trait。
这些规则称为“孤儿规则”。
(1) 为自定义类型实现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"
}
(2) 为外部类型(原始类型,比如i32等)实现自定义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"
}
(3)为自定义类型实现外部Trait:
// the `Neg` trait is used to overload `-`, the
// unary minus operator.
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; // this is only possible because we implemented `Neg`
println!("{}", m.value); // prints "-987"
}
一个impl
块总是对应着一个类型,因此在该块中,Self
对应这个类型:
impl std::ops::Neg for Number {
type Output = Self;
fn neg(self) -> Self {
Self {
value: -self.value,
odd: self.odd,
}
}
}
一些Trait是标签-它们不是说某一类型实现某些方法,而是说某些事情可以借助于某一类型来完成。比如说,i32
实现了Copy
Trait.(简言之,i32
是 Copy
),因此以下例子是可以正常工作的。
fn main() {
let a: i32 = 15;
let b = a; // `a` is copied
let c = a; // `a` is copied again
}
也可以这样:
fn print_i32(x: i32) {
println!("x = {}", x);
}
fn main() {
let a: i32 = 15;
print_i32(a); // `a` is copied
print_i32(a); // `a` is copied again
}
但是Number
结构体是不能如此操作的,因为其并没有实现Copy
Trait,所以这样做会有问题:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `n` is moved into `m`
let o = n; // error: use of moved value: `n`
}
也不能这样做:
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 moved
print_number(n); // error: use of moved value: `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
}
如果将函数参数换成可变引用也是能工作的,但只要变量绑定是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() {
// this time, `n` is mutable
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n); // `n is borrowed mutably - everything is explicit
print_number(&n);
}
Trait方法也可以引用或者可变引用作为自身:
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..self }
}
}
当调用Trait方法时,隐式借用了接收者:
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);
标签Trait像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.
}
有些Trait
可以通过derive
属性自动实现标签Trait:
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// this expands to `impl Clone for Number` and `impl Copy for Number` blocks.
函数泛型:
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 = {}");
}
约束可能更复杂:它们可能需要一个类型参数来实现多个Traits:
use std::fmt::Debug;
fn compare<T>(left: T, right: T)
where
T: Debug + PartialEq,
{
println!("{:?} {} {:?}", left, if left == right {"=="})
}
fn main() {
compare("tea", "coffee");
// prints: "tea" != "coffee"
}
泛型函数可以被认为是名称空间,其中包含无限个具有不同具体类型的函数。
和使用crate
, module
,type
和泛型函数一样,可以使用::
来“导航”;
fn main() {
use std::any::type_name;
println!("{}", type_name::<i32>()); // prints "i32"
println!("{}", type_name::<(f64, char)>()); // prints "(f64, char)"
}
这被称为涡轮鱼语法,因为::<>看起来像一条鱼。结构体也可以是泛型的:
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!
宏来定义:
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
一些方法也是Panic,比如Option
类型能为Some(x),也可能为None。如果Option
调用.unwrap(),如果为None,那么将会Panic。
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); //this is fine
let o2: Option<i32> = None;
o2.unwrap(); // this is panics!
}
// output: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21
Option
不是一个结构体,而是一个枚举类型,且只有两个变量;
enum Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
match self {
Self::Some(t) => t,
Self::None => panic!(".unwrap() called on a None option")
}
}
}
use self::Option::{None, Some};
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // this is fine
let o2: Option<i32> = None;
o2.unwrap(); // this panics!
}
// output: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21
Result
也是一个枚举类型:
enum Result<T, E> {
Ok(T),
Err(E),
}
它也会在Result值是Err(E)时发生panic。
Lifetime
变量绑定有一个生命周期“lieftime”:
fn main() {
// `x` doesn't exist yet
{
let x = 42; // `x` starts existing
println!("x = {}", x);
// `x` stops existing
}
// `x` no longer exists
}
类似地,引用有生命周期:
fn main() {
// `x` doesn't exist yet
{
let x = 42; // `x` starts existing
let x_ref = &x; // `x_ref` starts existing - it borrows `x`
println!("x_ref = {}", x_ref);
// `x_ref` stops existing
// `x` stops existing
}
// `x` no longer exists
}
引用的生命周期长度不能超过它所借用的变量绑定的生命周期:
fn main() {
let x_ref = {
let x = 42;
&x
};
println!("x_ref = {}", x_ref);
// error: `x` does not live long enough
}
不可变变量绑定可被借用多次:
fn main() {
let x = 42;
let x_ref1 = &x;
let x_ref2 = &x;
let x_ref3 = &x;
println!("{} {} {}", x_ref1, x_ref2, x_ref3);
}
当一个变量被借用,那么变量绑定将不可变:
fn main() {
let mut x = 42;
let x_ref = &x;
x = 13;
println!("x_ref = {}", x_ref);
// error: cannot assign to `x` because it is borrowed
}
当一个变量被不可变借用时,那么该变量不允许可变借用:
fn main() {
let mut x = 42;
let x_ref1 = &x;
let x_ref2 = &mut x;
// error: cannot borrow `x` as mutable because it is also borrowed as immutable
println!("x_ref1 = {}", x_ref1);
}
函数的引用参数也有生命周期:
fn print(x:&i32) {
// `x` is borrowed (from the outside) for the
// entire time this function is called.
}
具有引用参数的函数可以使用具有不同生命周期的借用来调用:
(1)All functions that take references are generic
(2)生命周期是泛型参数;
生命周期的名称起始字符带有单引号'
:
// elided (non-named) lifetimes:
fn print(x: &i32) {}
// named lifetimes:
fn print<'a>(x: &'a i32) {}
返回引用的生命周期要依赖于某一个函数参数的生命周期:
// elided (non-named) lifetimes:
fn print(x: &i32) {}
// named lifetimes:
fn print<'a>(x: &'a i32) {}
当函数只有一个输入生命周期(带有生命周期限制的函数参数),没有必要去标注生命周期,所有项都具有相同的生命周期,因此以下两个函数是等价的:
fn number_value<'a>(num: &'a Number) -> &'a i32 {
&num.value
}
fn number_value(num: &Number) -> &i32 {
&num.value
}
结构体在生命周期中也可以是泛型的,这使得结构体持有引用:
struct NumRef<'a> {
x: &'a i32,
}
fn main() {
let x:i32 = 99;
let x_ref = NumRef {x: &x};
// `x_ref` cannot outlive `x`, etc.
}
同样的代码,但是增加了一个函数:
struct NumRef<'a> {
x: &'a i32,
}
fn as_num_ref(x: &'a i32) -> NumRef<'a> {
NumRef {x: &x}
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` cannot outlive `x`, etc.
}
同样代码,但省略了生命周期:
struct NumRef<'a> {
x: &'a i32,
}
fn as_num_ref(x: &'a i32) -> NumRef<'_> {
NumRef {x: &x}
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` cannot outlive `x`, etc.
}s_
impl
块也可以泛型方式使用生命周期:
impl<'a> NumRef<'a> {
fn as_i32_ref(&'a self) -> &'a i32 {
self.x
}
}
fn main() {
let x: i32 = 99;
let x_num_ref = NumRef { x: &x};
let x_i32_ref = x_num_ref.as_i32_ref();
// neither ref cannot outlive `x`
}
当然,对于只有一个生命周期参数的情况,也可以省略:
impl<'a> NumRef<'a> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
如果没有显示的生命周期符号,那省略的将更加坚决:
impl NumRef<'_> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
特殊生命周期 static,其在程序的整个生命周期内都有效,以下为String
语法的例子:
struct Person {
name: &'static str,
}
fn main() {
let p = Person {
name: "fasterthanlime",
};
}
但是已有所主的字符串是不能static
的,以下例子中的引用生命周期是不能长于所引用变量的生命周期的:
struct Person {
name: &'static str,
}
fn main() {
let name = format!("fasterthan{}", lime);
let p = Person { name: &name };
// error: `name` does not live long enough
}
In that last example, the local name is not a &static str, it’s a String. It’s been allocated dynamically, and it will be freed. Its lifetime is less than the whole program (even though it happens to be in main)."
为了在Person
结构体中存储一个非‘static字符串,需要:
A)使用生命周期泛型:
struct Person<'a> {
name: &'a str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// `p` cannot outlive `name`
}
B) 获取字符串的所有权
struct Person {
name: String,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: name };
// `name` was moved into `p`, their lifetimes are no longer tied.
}
说起:在结构体中,当一个域被设置为变量绑定为相同的名称,(即值与域名称相同):
let p = Person { name: name};
可以简写为:
let p = Person { name };
Rust中很多类型有owned
和non-owned
变量:
(1)Strings:String
是owned, &str是引用的;
(2)Paths: PathBuf
是owned, &Path是引用的;
(3)Collections: Vec<T>
是owned, &[T]是引用的;
Rust有slice(切片),他们是多连续元素的引用。可以通过以下例子中的方式借用动态数组(vector):
fn main() {
let v = vec![1,2,3,4,5];
let v2 = &v[2..4];
println!("v2 = {:?}", v2);
}
//output:
// v2 = [3, 4]
以上列子并非难以理解,查询操作符(foo[index])重载了Index
和IndexMut
Trait。..
语义表示范围,仅仅是一些在标准库中定义的结构体。且索引范围是半开半闭区间内的元素,如果最右端的前面加上=
:
fn main() {
// 0 or greater
println!("{:?}", (0..).contains(&100)); // true
// strictly less than 20
println!("{:?}", (..20).contains(&20)); // false
// 20 or less than 20
println!("{:?}", (..=20).contains(&20)); // true
// only 3, 4, 5
println!("{:?}", (3..6).contains(&4)); // true
}
借用规则同样可应用于slices:
fn tail(s: &[u8]) -> &[u8] {
&s[1..]
}
fn main() {
let x = &[1, 2, 3, 4, 5];
let y = tail(x);
println!("y = {:?}", y);
}
相同效果的例子:
fn tail<'a>(s: &'a [u8]) -> &'a [u8] {
&s[1..]
}
这样是合法的:
fn main() {
let y = {
let x = &[1, 2, 3, 4, 5];
tail(x)
};
println!("y = {:?}", y);
}
因为[1,2,3,4,5]
是’static数组,因此,这是不合法的:
fn main() {
let y = {
let v = vec![1, 2, 3, 4, 5];
tail(&v)
// error: `v` does not live long enough
};
println!("y = {:?}", y);
}
因为动态数组是基于堆分配的,并没有‘static生命周期。
&str
值是切片
fn file_ext(name: &str) -> Option<&str> {
// this does not create a new string - it returns
// a slice of the argument.
name.split(".").last()
}
fn main() {
let name = "Read me. Or don't.txt";
if let Some(ext) = file_ext(name) {
println!("file extension: {}", ext);
} else {
println!("no file extension");
}
}
因此借用规则同样适用于此:
fn main() {
let ext = {
let name = String::from("Read me. Or don't.txt");
file_ext(&name).unwrap_or("")
// error: `name` does not live long enough
};
println!("extension: {:?}", ext);
}
函数执行失败时通常会返回一个Result
:
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]);
println!("{:?}", s);
// prints: Ok("🍉")
let s = std::str::from_utf8(&[195, 40]);
println!("{:?}", s);
// prints: Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
}
在执行失败时,若你想Panic, 那么调用.unwrap():
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
println!("{:?}", s);
// prints: "🍉"
let s = std::str::from_utf8(&[195, 40]).unwrap();
// prints: thread 'main' panicked at 'called `Result::unwrap()`
// on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }',
// src/libcore/result.rs:1165:5
}
或者想获取自定义的信息,可以调用.expect():
fn main() {
let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
// prints: thread 'main' panicked at 'valid utf-8: Utf8Error
// { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
}
或者使用match
fn main() {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => panic!(e),
}
// prints 🍉
}
或者使用if let
:
fn main() {
if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
println!("{}", s);
}
// prints 🍉
}
或者可以抛出错误:
fn main() -> Result<(), std::str::Utf8Error> {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => return Err(e),
}
Ok(())
}
或者可以使用操作符?
使得代码更加简洁:
fn main() -> Result<(), std::str::Utf8Error> {
let s = std::str::from_utf8(&[240, 159, 141, 137])?;
println!("{}", s);
Ok(())
}
解引用操作符*
,但你并不需要使用解引用来访问域或者调用方法:
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
println!("({}, {})", p_ref.x, p_ref.y);
}
// prints `(1, 3)`
并且当类型是Copy
语义时,你可以简单地这样做。
首先看下没有Copy
语义时,如下例子:
struct Point {
x: f64,
y: f64,
}
fn negate(p: Point) -> Point {
Point {
x: -p.x,
y: -p.y,
}
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref);
// error: cannot move out of `*p_ref` which is behind a shared reference
}
但当有Copy
语义时:
// now `Point` is `Copy`
#[derive(Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
fn negate(p: Point) -> Point {
Point {
x: -p.x,
y: -p.y,
}
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref); // ...and now this works
}
闭包
闭包是具有某些捕获上下文的Fn,FnMut或FnOnce类型的函数。它们的参数是一对管道内(’|’),逗号分隔名称列表。它们不需要花括号,除非要使用多个语句。
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
for_each_planet(|planet| println!("Hello, {}", planet));
}
// prints:
// Hello, Earth
// Hello, Mars
// Hello, Jupiter
借用规则同样适用于这里:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// our closure borrows `greeting`, so it cannot outlive it
}
比如,以下例子就不能正常通过:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str) + 'static // `F` must now have "'static" lifetime
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// error: closure may outlive the current function, but it borrows
// `greeting`, which is owned by the current function
}
但是这样是可以的:
fn main() {
let greeting = String::from("You're doing great");
for_each_planet(move |planet| println!("{}, {}", greeting, planet));
// `greeting` is no longer borrowed, it is *moved* into
// the closure.
}
一个FnMut
需要可变借用才能被调用,因此它在某一时刻只能被调用一次,比如以下例子是合法的:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
而这个例子是非法的:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
println!("{}", f(f(2)));
// error: cannot borrow `f` as mutable more than once at a time
}
fn main() {
foobar(|x| x * 2);
}
这样操作又变成合法的了:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
FnMut
存在因为一些闭包可变借用局部变量:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
x * acc
});
}
// output: 24
这些闭包不能被传给需要Fn
的函数:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
// error: cannot assign to `acc`, as it is a
// captured variable in a `Fn` closure.
// the compiler suggests "changing foobar
// to accept closures that implement `FnMut`"
x * acc
});
}
FnOnce
闭包只能被调用一次。它们之所以存在,是因为某些闭包将捕获时移出的变量移出:
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
}
fn main() {
let s = String::from("alright");
foobar(move || s);
// `s` was moved into our closure, and our
// closures moves it to the caller by returning
// it. Remember that `String` is not `Copy`.
}
这自然是强制执行的,因为需要移动FnOnce闭包才能调用它,因此,以下例子是非法的:
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
println!("{}", f());
// error: use of moved value: `f`
}
并且,如果你需要有说服力的证据表明闭包确实会移动s
,那么下面这个例子也是非法的:
fn main() {
let s = String::from("alright");
foobar(move || s);
foobar(move || s);
// use of moved value: `s`
}
但是这样却是可以的:
fn main() {
let s = String::from("alright");
foobar(|| s.clone());
foobar(|| s.clone());
}
这里有一个带有两个参数的闭包:
fn foobar<F>(x: i32, y: i32, is_greater: F)
where F: Fn(i32, i32) -> bool
{
let (greater, smaller) = if is_greater(x, y) {
(x, y)
} else {
(y, x)
};
println!("{} is greater than {}", greater, smaller);
}
fn main() {
foobar(32, 64, |x, y| x > y);
}
闭包允许忽略参数:
fn main() {
foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}
稍微令人担心的闭包:
fn countdown<F>(count: usize, tick: F)
where F: Fn(usize)
{
for i in (1..=count).rev() {
tick(i);
}
}
fn main() {
countdown(3, |i| println!("tick {}...", i));
}
// output:
// tick 3...
// tick 2...
// tick 1...
"马桶式"闭包(之所以这么叫是因为|_| ()
看起来像个马桶):
fn main() {
countdown(3, |_| ());
}
循环
任何可迭代的元素集合均可使用类似于for in
循环。我们知道在某一范围内可以使用该形式的循环,其实在Vec
中也是可以使用的:
fn main() {
for i in vec![52, 49, 21] {
println!("I like the number {}", i);
}
}
或者是切片(slice)
fn main() {
for i in &[52, 49, 21] {
println!("I like the number {}", i);
}
}
// output:
// I like the number 52
// I like the number 49
// I like the number 21
或者是一个实际的迭代器:
fn main() {
// note: `&str` also has a `.bytes()` iterator.
// Rust's `char` type is a "Unicode scalar value"
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
// output:
// Give me a r
// Give me a u
// Give me a s
// Give me a t
即使迭代器项已被过滤,映射或者展开:
fn main() {
for c in "sHE'S brOKen"
.chars()
.filter(|c| c.is_uppercase() || !c.is_ascii_alphabetic())
.flat_map(|c| c.to_lowercase())
{
print!("{}", c);
}
println!();
}
// output: he's ok
依旧可以从一个函数返回一个闭包:
fn make_tester(answer: String) -> impl Fn(&str) -> bool {
move |challenge| {
challenge == answer
}
}
fn main() {
// you can use `.into()` to perform conversions
// between various types, here `&'static str` and `String`
let test = make_tester("hunter2".into());
println!("{}", test("******"));
println!("{}", test("hunter2"));
}
你甚至能移动函数某一参数的引用到函数返回的闭包中:
fn make_tester<'a>(answer: &'a str) -> impl Fn(&str) -> bool + 'a {
move |challenge| {
challenge == answer
}
}
fn main() {
let test = make_tester("hunter2");
println!("{}", test("*******"));
println!("{}", test("hunter2"));
}
// output:
// false
// true
或者带有省略生命周期的形式:
fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
move |challenge| {
challenge == answer
}
}
这样一来,我们达到了预计30分钟阅读时间的计划,你应该能够阅读大部分在网上找到的Rust代码。
编写Rust代码跟阅读Rust是两种截然不同的体验,一方面是你并不是阅读一个问题的解决方案,你要去解决该问题,另一方面,Rust编译器可以提供很多帮助。
对于上述所有有意而为之的错误代码(“此代码是非法的”等),rustc始终具有非常好的错误消息和有见地的建议。而且,当缺少提示时,编译器团队不会害怕添加它。
也许你需要查看更多的资料:
The Rust Book
Rust By Example
Read Rust
This Week In Rust
我也写一些Rust相关的博客和发一些Rust相关的twitter,因此如果你比较喜欢这篇文章,你知道该怎么做了!
Have fun!