原文: A half-hour to learn Rust
为了熟练的掌握一门编程语言,人们不得不阅读它的大量的相关资料。但是如果你不理解这些资料介绍的内容,如何能学习更多的资料呢?
在本文中,我将尽可能多地列举Rust代码片段,并解释它们包含的关键字和符号的含义,而不是只关注Rust的一两个概念。
准备好了吗,让我们出发吧!
let
引入变量
也可以写成一行:
你可以使用:
显式地指定变量的类型,也就是类型注解:
也可以写成一行:
如果你声明一个变量,后来再初始化它。在初始化之前使用它的话编译器会报错:
1 2 3
| let x; foobar(x); x = 42;
|
下面的代码就可以了:
1 2 3
| let x; x = 42; foobar(x);
|
下划线_
是一个特殊的名字,或者更确切地说,“缺乏名字”。基本上它的意思就是扔掉一些东西:
1 2 3 4 5
| let _ = 42; let _ = get_thing();
|
以下划线开头的名称是常规名称,有一点特殊,就是如果它们未被使用的话编译器不会报警告:
变量名可以重用-它会隐藏(shadow)前一个变量:
1 2 3 4
| let x = 13; let x = x + 3;
|
Rust有tuple类型,你可以把它看作有固定长度的不同类型的集合:
1 2 3
| let pair = ('a', 17); pair.0; pair.1;
|
如果我们想为tuple加上类型注解,可以这么做:
1
| let pair: (char, i32) = ('a', 17);
|
Tuple类型可以通过赋值方式进行解构(destructured),这意味着它们被分成各自独立的字段:
1 2
| let (some_char, some_int) = ('a', 17);
|
当一个函数返回tuple类型的时候特别管用:
1
| let (left, right) = slice.split_at(middle);
|
当然,解构一个tuple的时候,下划线_
可以用来丢掉一些字段:
1
| let (_, right) = slice.split_at(middle);
|
分号;
放在语句(statement)的结尾:
1 2 3
| let x = 3; let y = 5; let z = y + x;
|
这意味着语句可以写成多行:
1 2 3 4
| let x = vec![1, 2, 3, 4, 5, 6, 7, 8] .iter() .map(|x| x + 3) .fold(0, |x, y| x + y);
|
(之后我们再介绍这段代码的意义)
fn
用来声明一个函数。
下面是一个void函数:
1 2 3
| fn greet() { println!("Hi there!"); }
|
下面是一个返回32位有符号的整数,箭头指示它的返回类型:
1 2 3
| fn fair_dice_roll() -> i32 { 4 }
|
一对大括号声明了代码块(block),块有自己的作用域:
1 2 3 4 5 6 7 8 9 10
| fn main() { let x = "out"; { let x = "in"; println!(x); } println!(x); }
|
块也是表达式,意味着它的计算结果是一个值:
1 2 3 4 5
| let x = 42; let x = { 42 };
|
块可以包括多条语句:
1 2 3 4 5
| let x = { let y = 1; let z = 2; y + z };
|
函数块的最后省略分号意味着返回这个值,例如下面两个函数功能是一样的:
1 2 3 4 5 6 7
| fn fair_dice_roll() -> i32 { return 4; } fn fair_dice_roll() -> i32 { 4 }
|
if
条件也可以是表达式:
1 2 3 4 5 6 7
| fn fair_dice_roll() -> i32 { if feeling_lucky { 6 } else { 4 } }
|
match
也是表达式:
1 2 3 4 5 6
| fn fair_dice_roll() -> i32 { match feeling_lucky { true => 6, false => 4, } }
|
点号(.
)用来访问一个值的字段:
1 2 3 4 5
| let a = (10, 20); a.0; let amos = get_some_struct(); amos.nickname;
|
或者调用一个方法:
1 2
| let nick = "fasterthanlime"; nick.len();
|
双冒号(::
)类似点号但是操作的对象是命名空间。
下面的例子中std
是一个crate(库),cmp
是一个模块(module
, 源文件),min
是一个函数:
1
| let least = std::cmp::min(3, 8);
|
use
指令将其它命名空间的名称引入到当前:
1 2 3
| use std::cmp::min; let least = min(7, 1);
|
使用use
指令的时候,大括号意味着一组名称(glob
)。如果我们想同时引入max
和min
,我们可以这么做:
1 2 3 4 5 6 7 8 9
| use std::cmp::min; use std::cmp::max; use std::cmp::{min, max}; use std::{cmp::min, cmp::max};
|
通配符*
引入命名空间下的所有的名称:
类型也是命名空间,它们的方法也可以通过普通函数一样调用:
1 2
| let x = "amos".len(); let x = str::len("amos");
|
str
是一个基本类型(primitive type),但是在默认的命名空间下也有很多非基本类型:
1 2 3 4 5
| let v = Vec::new(); let v = std::vec::Vec::new();
|
这是因为Rust会在每个模块开始之前插入:
1
| use std::prelude::v1::*;
|
它重新输出了很多的符号,比如Vec
、String
、Option
和Result
。
结构体使用struct
声明:
1 2 3 4
| struct Vec2 { x: f64, y: f64, }
|
可以使用结构体文本初始化它们:
1 2 3
| let v1 = Vec2 { x: 1.0, y: 3.0 }; let v2 = Vec2 { y: 2.0, x: 4.0 };
|
有一个简洁的方式使用另外一个结构体初始化余下的字段:
1 2 3 4
| let v3 = Vec2 { x: 14.0, ..v2 };
|
这被称作struct update syntax
,只能发生在最后的位置,后面没有逗号。
注意"剩余的字段"可以是结构体的所有字段:
结构体,也像tuple类型一样,可以解构。
下面是一个合法的let
模式:
1
| let (left, right) = slice.split_at(middle);
|
又比如下面的代码:
1 2 3
| let v = Vec2 { x: 3.0, y: 6.0 }; let Vec2 { x, y } = v;
|
抑或下面的代码:
1 2
| let Vec2 { x, .. } = v;
|
let
模式也可以用在if
中当条件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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); } }
|
match
匹配也是模式,就像if let
:
1 2 3 4 5 6 7 8
| fn print_number(n: Number) { match n { Number { odd: true, value } => println!("Odd number: {}", value), Number { odd: false, value } => println!("Even number: {}", value), } }
|
注意match
匹配必须是详尽的,至少需要一个分支与之匹配:
1 2 3 4 5 6 7 8
| fn print_number(n: Number) { match n { Number { value: 1, .. } => println!("One"), Number { value: 2, .. } => println!("Two"), Number { value, .. } => println!("{}", value), } }
|
如果你觉得麻烦,可以使用下划线_
匹配所有的模式:
1 2 3 4 5 6 7
| fn print_number(n: Number) { match n.value { 1 => println!("One"), 2 => println!("Two"), _ => println!("{}", n.value), } }
|
你可以为你的类型声明方法:
1 2 3 4 5 6 7 8 9 10
| struct Number { odd: bool, value: i32, } impl Number { fn is_strictly_positive(self) -> bool { self.value > 0 } }
|
可以正常使用它们:
1 2 3 4 5 6 7 8
| fn main() { let minus_two = Number { odd: false, value: -2, }; println!("positive? {}", minus_two.is_strictly_positive()); }
|
默认变量绑定是不可变的:
1 2 3 4 5 6 7 8
| fn main() { let n = Number { odd: true, value: 17, }; n.odd = false; }
|
不可变的变量不能对其变量值进行修改,但是同时也不能通过赋值更改变量:
1 2 3 4 5 6 7 8 9 10
| fn main() { let n = Number { odd: true, value: 17, }; n = Number { odd: false, value: 22, }; }
|
mut
允许变量可以更改:
1 2 3 4 5 6 7
| fn main() { let mut n = Number { odd: true, value: 17, } n.value = 19; }
|
trait
是多个类型拥有的共同的东西:
1 2 3
| trait Signed { fn is_strictly_negative(self) -> bool; }
|
你可以实现:
- 为任意类型实现你自己定义的trait
- 为你的类型实现任意类型的trait
- 不允许为别人的类型实现别人的trait
这被称之为孤立规则
(orphan rules)。
下面的例子是自定义类型实现自定义trait:
1 2 3 4 5 6 7 8 9 10
| 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()); }
|
为外部类型实现自定义trait(甚至是基本类型):
1 2 3 4 5 6 7 8 9 10
| impl Signed for i32 { fn is_strictly_negative(self) -> bool { self < 0 } } fn main() { let n: i32 = -44; println!("{}", n.is_strictly_negative()); }
|
为自定义类型实现外部trait:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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; println!("{}", m.value); }
|
impl
总是用来为类型实现方法的,所以在这个块中,Self
意味着这个类型:
1 2 3 4 5 6 7 8 9 10
| impl std::ops::Neg for Number { type Output = Self; fn neg(self) -> Self { Self { value: -self.value, odd: self.odd, } } }
|
有些trait只是一个标记(marker
),它并不是指示类型要实现什么方法,而是说这种类型可以用作做特定的事情。
例如i32
实现Copy
trait(简单地讲,i32
是可复制的),所以下面的代码工作正常:
1 2 3 4 5
| fn main() { let a: i32 = 15; let b = a; let c = a; }
|
下面的代码也正常:
1 2 3 4 5 6 7 8 9
| fn print_i32(x: i32) { println!("x = {}", x); } fn main() { let a: i32 = 15; print_i32(a); print_i32(a); }
|
但是Number
类型没有实现Copy
,所以下面的代码不工作:
1 2 3 4 5
| fn main() { let n = Number { odd: true, value: 51 }; let m = n; let o = n; }
|
下面的代码也不行:
1 2 3 4 5 6 7 8 9
| 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); print_number(n); }
|
但是如果print_number
使用一个不可变的引用就可以:
1 2 3 4 5 6 7 8 9
| 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); print_number(&n); }
|
如果变量被声明为可变的,则函数参数使用可变引用也可以工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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() { let mut n = Number { odd: true, value: 51 }; print_number(&n); invert(&mut n); print_number(&n); }
|
Trait方法中的self
参数可以使用引用,也可以使用不可变引用。
1 2 3 4
| impl std::clone::Clone for Number { fn clone(&self) -> Self { Self { ..*self } }
|
当调用trait的方法时,receiver
隐式地被借用:
1 2 3 4 5 6 7 8
| fn main() { let n = Number { odd: true, value: 51 }; let mut m = n.clone(); m.value += 100; print_number(&n); print_number(&m); }
|
前面讲到过,它和下面的代码一样:
1 2 3
| let m = n.clone(); let m = std::clone::Clone::clone(&n);
|
像Copy
这样的Marker trait是没有方法的:
1 2 3 4 5 6 7 8
| impl std::clone::Clone for Number { fn clone(&self) -> Self { Self { ..*self } } } impl std::marker::Copy for Number {}
|
现在Clone
仍然可以使用:
1 2 3 4 5
| fn main() { let n = Number { odd: true, value: 51 }; let m = n.clone(); let o = n.clone(); }
|
但是Number
值不会被move
了:
1 2 3 4 5
| fn main() { let n = Number { odd: true, value: 51 }; let m = n; let o = n; }
|
一些trait太通用了,我们可以通过derive
属性自动实现它们:
1 2 3 4 5 6 7
| #[derive(Clone, Copy)] struct Number { odd: bool, value: i32, }
|
函数可以是泛型的:
1 2 3
| fn foobar<T>(arg: T) { }
|
它们可以有多个类型参数,类型参数用在函数声明和函数体中,用来替代具体的类型:
1 2 3
| fn foobar<L, R>(left: L, right: R) { }
|
类型参数通常有约束,所以你可以用它做一些额外的事情。
最简单的约束就是trait名称:
1 2 3 4 5 6 7
| fn print<T: Display>(value: T) { println!("value = {}", value); } fn print<T: Debug>(value: T) { println!("value = {:?}", value); }
|
类型约束还可以使用更长的语法:
1 2 3 4 5 6
| fn print<T>(value: T) where T: Display, { println!("value = {}", value); }
|
约束还可以更复杂,比如要求类型要实现多个trait:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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"); }
|
泛型函数可以被当作一个命名空间,包含无穷多个不同具体类型的函数。
类似crate
、module
和类型,泛型函数可以使用::
导航:
1 2 3 4 5
| fn main() { use std::any::type_name; println!("{}", type_name::<i32>()); println!("{}", type_name::<(f64, char)>()); }
|
这被亲切地称之为turbofish syntax,因为::<>
看起来像条鱼。
结构体可以是泛型的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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); print_type_name(&p2); }
|
标准库中的Vec
(分配在堆上的数组)就是泛型实现的:
1 2 3 4 5 6 7 8
| fn main() { let mut v1 = Vec::new(); v1.push(1); let mut v2 = Vec::new(); v2.push(false); print_type_name(&v1); print_type_name(&v2); }
|
谈到Vec
,有个宏(macro
)可以通过字面方式声明Vec
变量:
1 2 3 4 5 6
| fn main() { let v1 = vec![1, 2, 3]; let v2 = vec![true, false, true]; print_type_name(&v1); print_type_name(&v2); }
|
类似name!()
、name![]
、name!{}
都是调用宏的方式。宏会被展开成正常的代码。
事实上,println
就是一个宏:
1 2 3
| fn main() { println!("{}", "Hello there!"); }
|
它的展开代码和下面的代码功能一样:
1 2 3 4
| fn main() { use std::io::{self, Write}; io::stdout().lock().write_all(b"Hello there!\n").unwrap(); }
|
panic
也是一个宏,例如Option
既可以包含某个值,也可以不包含值。如果它不包含值,调用它的.unwrap()
会panic:
1 2 3 4 5 6 7 8 9
| fn main() { let o1: Option<i32> = Some(128); o1.unwrap(); let o2: Option<i32> = None; o2.unwrap(); }
|
Option
并不是一个结构体,而是一个枚举类型(enum
),它包含两个值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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(); let o2: Option<i32> = None; o2.unwrap(); }
|
Result
也是一个枚举类型。它既可以包含一个正常的,也可以包含一个error:
1 2 3 4
| enum Result<T, E> { Ok(T), Err(E), }
|
如果包含error,调用它的.unwrap()
也会panic。
变量绑定有声明周期
:
1 2 3 4 5 6 7 8 9
| fn main() { { let x = 42; println!("x = {}", x); } }
|
类似地,引用也有声明周期:
1 2 3 4 5 6 7 8 9 10 11
| fn main() { { let x = 42; let x_ref = &x; println!("x_ref = {}", x_ref); } }
|
引用的声明周期不能超过它借用的变量的声明周期:
1 2 3 4 5 6 7 8
| fn main() { let x_ref = { let x = 42; &x }; println!("x_ref = {}", x_ref); }
|
一个变量可以不可变地借用多次:
1 2 3 4 5 6 7
| fn main() { let x = 42; let x_ref1 = &x; let x_ref2 = &x; let x_ref3 = &x; println!("{} {} {}", x_ref1, x_ref2, x_ref3); }
|
在借用的时候,变量不能被修改:
1 2 3 4 5 6 7
| fn main() { let mut x = 42; let x_ref = &x; x = 13; println!("x_ref = {}", x_ref); }
|
当不可变地借用时,不能同时可变地的借用:
1 2 3 4 5 6 7
| fn main() { let mut x = 42; let x_ref1 = &x; let x_ref2 = &mut x; println!("x_ref1 = {}", x_ref1); }
|
函数参数中的引用也有生命周期:
1 2 3 4
| fn print(x: &i32) { }
|
函数中的参数被调用时可以同时使用多个的生命周期:
- 所有使用这些引用的函数都是泛型的
- 声明周期也是泛型参数
生命周期的名称以'
开始:
1 2 3 4 5
| fn print(x: &i32) {} fn print<'a>(x: &'a i32) {}
|
返回引用的声明周期依赖参数的声明周期:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct Number { value: i32, } fn number_value<'a>(num: &'a Number) -> &'a i32 { &num.value } fn main() { let n = Number { value: 47 }; let v = number_value(&n); }
|
当只有一个生命周期时,它并需要被命名,所有对象都有同样的声明周期,所以下面两个函数是等价的:
1 2 3 4 5 6 7
| fn number_value<'a>(num: &'a Number) -> &'a i32 { &num.value } fn number_value(num: &Number) -> &i32 { &num.value }
|
结构体也可以通过生命周期声明为泛型,这允许它们持有引用:
1 2 3 4 5 6 7 8 9
| struct NumRef<'a> { x: &'a i32, } fn main() { let x: i32 = 99; let x_ref = NumRef { x: &x }; }
|
同样的代码,增加一个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct NumRef<'a> { x: &'a i32, } fn as_num_ref<'a>(x: &'a i32) -> NumRef<'a> { NumRef { x: &x } } fn main() { let x: i32 = 99; let x_ref = as_num_ref(&x); }
|
同样的代码,使用省略的(elided
)的生命周期:
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct NumRef<'a> { x: &'a i32, } fn as_num_ref(x: &i32) -> NumRef<'_> { NumRef { x: &x } } fn main() { let x: i32 = 99; let x_ref = as_num_ref(&x); }
|
impl
块也可以使用声明周期实现泛型:
1 2 3 4 5 6 7 8 9 10 11 12
| 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(); }
|
但是你同样可以使用省略的方式:
1 2 3 4 5
| impl<'a> NumRef<'a> { fn as_i32_ref(&self) -> &i32 { self.x } }
|
如果你不需要使用声明周期的名字,你甚至可以省略更多:
1 2 3 4 5
| impl NumRef<'_> { fn as_i32_ref(&self) -> &i32 { self.x } }
|
有一个特殊的声明周期,叫做'static
,它的生命周期在整个程序运行时。
字符串字面值就是'static
:
1 2 3 4 5 6 7 8 9
| struct Person { name: &'static str, } fn main() { let p = Person { name: "fasterthanlime", }; }
|
但是owned string
声明周期就不是'static
的:
1 2 3 4 5 6 7 8 9
| struct Person { name: &'static str, } fn main() { let name = format!("fasterthan{}", "lime"); let p = Person { name: &name }; }
|
上面的例子中name
并不是&'static str
类型,而是Stirng
类型。它是动态分配的,可以被释放。它的生命周期小于整个程序,尽管它是在main
函数中。
为了在Person
中存储一个非'static
的字符串,你需要:
A) 通过声明周期声明泛型
1 2 3 4 5 6 7 8 9
| struct Person<'a> { name: &'a str, } fn main() { let name = format!("fasterthan{}", "lime"); let p = Person { name: &name }; }
|
或者
B) 获得这个字符串的所有权
1 2 3 4 5 6 7 8 9
| struct Person { name: String, } fn main() { let name = format!("fasterthan{}", "lime"); let p = Person { name: name }; }
|
在一个结构体的字面值中,如果字段名和变量相同时:
1
| let p = Person { name: name };
|
可以简写为:
1
| let p = Person { name };
|
Rust中很多类型都有owned
和非owned
变种:
- 字符串:
String
是owned, &str
是引用
- 路径:
PathBuf
是owned, &Path
是引用
- 集合:
Vec<T>
是owned, &[T]
是引用
Rust有slice - 它们是对多个连续元素的引用。
你可以借用vector的slice,例如:
1 2 3 4 5 6 7 8
| fn main() { let v = vec![1, 2, 3, 4, 5]; let v2 = &v[2..4]; println!("v2 = {:?}", v2); }
|
上面并没有什么魔法。索引操作符(foo[index]
)被Index
和IndexMut
trait重载。
..
是range表示方法。 Range是标准库定义的一组结构体。
前后的索引值可以省略,默认右边的值是不包含的, 如果要包含右边的值,使用=
,:
1 2 3 4 5 6 7 8 9 10
| fn main() { println!("{:?}", (0..).contains(&100)); println!("{:?}", (..20).contains(&20)); println!("{:?}", (..=20).contains(&20)); println!("{:?}", (3..6).contains(&4)); }
|
借用规则同样应用于slice:
1 2 3 4 5 6 7 8 9
| fn tail(s: &[u8]) -> &[u8] { &s[1..] } fn main() { let x = &[1, 2, 3, 4, 5]; let y = tail(x); println!("y = {:?}", y); }
|
与下面的代码相同:
1 2 3
| fn tail<'a>(s: &'a [u8]) -> &'a [u8] { &s[1..] }
|
下面的代码是合法的:
1 2 3 4 5 6 7
| fn main() { let y = { let x = &[1, 2, 3, 4, 5]; tail(x) }; println!("y = {:?}", y); }
|
只不过是因为[1, 2, 3, 4, 5]
是'static
。下面的代码就不合法:
1 2 3 4 5 6 7 8
| fn main() { let y = { let v = vec![1, 2, 3, 4, 5]; tail(&v) }; println!("y = {:?}", y); }
|
这是因为vector
分配在堆上,它有非'static
的声明周期。
&str
实际上是slice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| fn file_ext(name: &str) -> Option<&str> { 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"); } }
|
借用规则同样适用:
1 2 3 4 5 6 7 8
| fn main() { let ext = { let name = String::from("Read me. Or don't.txt"); file_ext(&name).unwrap_or("") }; println!("extension: {:?}", ext); }
|
返回失败的函数典型地返回Result
:
1 2 3 4 5 6 7 8 9
| fn main() { let s = std::str::from_utf8(&[240, 159, 141, 137]); println!("{:?}", s); let s = std::str::from_utf8(&[195, 40]); println!("{:?}", s); }
|
如果处理失败的时候想panic,你可以调用.unwrap()
:
1 2 3 4 5 6 7 8 9 10
| fn main() { let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap(); println!("{:?}", s); let s = std::str::from_utf8(&[195, 40]).unwrap(); }
|
或者调用.expect()
panic一个定制的信息:
1 2 3 4 5
| fn main() { let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8"); }
|
抑或使用match
:
1 2 3 4 5 6 7
| fn main() { match std::str::from_utf8(&[240, 159, 141, 137]) { Ok(s) => println!("{}", s), Err(e) => panic!(e), } }
|
甚至使用if let
:
1 2 3 4 5 6
| fn main() { if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) { println!("{}", s); } }
|
再不济给上层抛出错误:
1 2 3 4 5 6 7
| 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(()) }
|
常用的简洁方式是使用?
:
1 2 3 4 5
| fn main() -> Result<(), std::str::Utf8Error> { let s = std::str::from_utf8(&[240, 159, 141, 137])?; println!("{}", s); Ok(()) }
|
*
符号常用来解引用,但是你不需要专门访问字段或者调用方法:
1 2 3 4 5 6 7 8 9 10 11 12
| 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); }
|
如果类型是可复制的(实现Copy
),那么你可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #[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); }
|
闭包(Closure
)是实现了Fn
、FnMut
、FnOnce
类型的函数。并且带有捕获的上下文。
参数是以逗号分隔的名称列表,在|
之中。它们不需要大括号,除非你有多行语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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)); }
|
借用规则同样适用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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)); }
|
比如下面的代码就不工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| fn for_each_planet<F>(f: F) where F: Fn(&'static str) + 'static { f("Earth"); f("Mars"); f("Jupiter"); } fn main() { let greeting = String::from("Good to see you"); for_each_planet(|planet| println!("{}, {}", greeting, planet)); }
|
但是下面的代码就可以:
1 2 3 4 5 6
| fn main() { let greeting = String::from("You're doing great"); for_each_planet(move |planet| println!("{}, {}", greeting, planet)); }
|
FnMut
需要可变地借用去调用,所以它同时只能调用一次。
下面的代码合法:
1 2 3 4 5 6 7 8 9 10 11
| fn foobar<F>(f: F) where F: Fn(i32) -> i32 { println!("{}", f(f(2))); } fn main() { foobar(|x| x * 2); }
|
下面的代码不合法:
1 2 3 4 5 6 7 8 9 10
| fn foobar<F>(mut f: F) where F: FnMut(i32) -> i32 { println!("{}", f(f(2))); } fn main() { foobar(|x| x * 2); }
|
然后下民的代码有合法了:
1 2 3 4 5 6 7 8 9 10 11 12
| fn foobar<F>(mut f: F) where F: FnMut(i32) -> i32 { let tmp = f(2); println!("{}", f(tmp)); } fn main() { foobar(|x| x * 2); }
|
之所以有FnMut
类似,是因为有些闭包会可变地借用本地变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 }); }
|
这些闭包不能传给期望Fn
类型的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| fn foobar<F>(f: F) where F: Fn(i32) -> i32 { println!("{}", f(f(2))); } fn main() { let mut acc = 2; foobar(|x| { acc += 1; x * acc }); }
|
FnOnce
闭包只会被调用一次。因为一些闭包会把move进来的变量在返回值中move out:
1 2 3 4 5 6 7 8 9 10 11 12 13
| fn foobar<F>(f: F) where F: FnOnce() -> String { println!("{}", f()); } fn main() { let s = String::from("alright"); foobar(move || s); }
|
这是自然限制的,因为FnOnce
闭包在调用的时候需要被move:
1 2 3 4 5 6 7
| fn foobar<F>(f: F) where F: FnOnce() -> String { println!("{}", f()); println!("{}", f()); }
|
而且你确保使用move
调用闭包,它依然是非法的:
1 2 3 4 5 6
| fn main() { let s = String::from("alright"); foobar(move || s); foobar(move || s); }
|
但是下面的代码就是合法的:
1 2 3 4 5
| fn main() { let s = String::from("alright"); foobar(|| s.clone()); foobar(|| s.clone()); }
|
下面是有两个参数的闭包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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); }
|
下面是忽略了这两个参数的闭包:
1 2 3
| fn main() { foobar(32, 64, |_, _| panic!("Comparing is futile!")); }
|
下面是一个唱“滴答”的闭包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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)); }
|
下面是一个马桶闭包:
1 2 3
| fn main() { countdown(3, |_| ()); }
|
|_| ()
看起来像不像一个🚽?
任意的迭代的东西都可以放在for in
循环中。
前面我们已经看到了range,也可以使用for in
操作Vec
:
1 2 3 4 5
| fn main() { for i in vec![52, 49, 21] { println!("I like the number {}", i); } }
|
或者处理slice:
1 2 3 4 5 6 7 8 9 10
| fn main() { for i in &[52, 49, 21] { println!("I like the number {}", i); } }
|
或者是一个实际的迭代器:
1 2 3 4 5 6 7 8 9 10 11 12 13
| fn main() { for c in "rust".chars() { println!("Give me a {}", c); } }
|
即使迭代器的元素被加上了过滤、映射、展开等操作:
1 2 3 4 5 6 7 8 9 10 11 12
| 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!(); }
|
你可以在一个函数中返回一个闭包:
1 2 3 4 5 6 7 8 9 10 11 12 13
| fn make_tester(answer: String) -> impl Fn(&str) -> bool { move |challenge| { challenge == answer } } fn main() { let test = make_tester("hunter2".into()); println!("{}", test("******")); println!("{}", test("hunter2")); }
|
甚至你可以move函数的参数的引用到闭包中返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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")); }
|
省略生命周期名称:
1 2 3 4 5
| fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ { move |challenge| { challenge == answer } }
|
好了,半小时到了,通过本次学习,你应该能通过线上大部分的rust代码。
写rust代码和读rust又有很大的不同。一方面你不是在读一个问题的解决访问,而是正在解决它;另一方面Rust编译器会给你很大的帮助。
对于上面非法的rust代码,rustc总是会给你清楚的错误提示和富有洞察力的建议。
如果提示缺失,编译器组欢迎你提出来。
如果需要更多的Rust学习资料,你可以访问下面的资源:
我也会写一些关于rust的博文和tweet。
阅读愉快。
blablabla一堆感谢。