参考 Rust Documentation ,Tour of Rust ,crates.io 。
Getting Started
Hello, World!
1 2 3 fn main () { println! ("Hello, world!" ); }
1 2 3 $ rustc main.rs $ ./main Hello, world!
Common Programming Concepts
Variables and Mutability
使用 let
声明变量,变量默认是不可变的,使用 mut
关键字使其可变。使用 const
声明常量,不允许对常量使用 mut
,并且必须显示指定其类型。
1 2 let mut x = 5 ;const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3 ;
和其他语言不同,Rust 允许在相同作用域中遮蔽(shadowing)变量,而且允许使用不同的类型。
1 2 let spaces = " " ;let spaces = spaces.len ();
Data Types
Rust 数据类型分为两种:标量类型(scalar)和复合类型(compound)。有四种基本的标量类型:整型、浮点类型、布尔类型和字符类型。编译器通常可以推断出类型,如果不能则需要显式写出类型。
1 let guess : u32 = "42" .parse ().expect ("Not a number!" );
Scalar Types
有符号整型有 i8
、i16
、i32
、i64
、i128
、isize
,其中 isize
类型和机器相关。无符号整型类似,只是将 i
开头替换为 u
开头。整型字面量:十进制 98_222
、十六进制 0xff
、八进制 0o77
、二进制 0b1111_0000
和单字节字符 b'A'
(仅限 u8
类型)。可以使用标准库函数显示处理整型溢出:wrapping_*
、checked_*
、overflowing_*
和 saturating_*
。
浮点类型有 f32
和 f64
,默认使用 f64
。布尔类型 bool
的值有 true
和 false
两种。字符类型 char
大小为 4 字节,使用 UTF-32 编码方式。
Compound Types
Rust 有两个原生的复合类型,元组(tuple)和数组(array)。元组可以将一个或多个不同类型的值组合起来,声明之后长度固定不变。可以使用 .index
、模式匹配(pattern matching)和解构(destructure)获取元组元素,其中 index
是元素的索引。不包含任何值的元组 ()
被称为单元(unit),表示空值或空返回类型,如果表达式不返回其他值,则会隐式地返回单元值。
1 2 3 4 let x : (i32 , f64 , u8 ) = (500 , 6.4 , 1 );let five_hundred = x.0 ;let six_point_four = x.1 ;let one = x.2 ;
1 2 3 let tup = (500 , 6.4 , 1 );let (x, y, z) = tup;println! ("The value of y is: {y}" );
数组中每个元素的类型都必须相同,数组长度也是固定的,在栈上分配空间。在声明类型时,需要在方括号中包含元素类型和数量。在初始化时,可以使用方括号包含初始值和元素数量,创建每个元素都相同的数组。
1 2 let a : [i32 ; 5 ] = [1 , 2 , 3 , 4 , 5 ];let a = [3 ; 5 ];
Functions & Control Flow
Rust 代码中的变量和函数名使用 snake case 风格,使用下划线分隔单词。Rust 是基于表达式的语言(expression-based),函数体由一系列语句(statement)和可选地结尾表达式(expression)组成,语句不会返回值而表达式会。表达式结尾没有分号,如果加上分号它就变为语句。如果函数没有返回值,则可以省略返回类型,会隐式地返回空元组,否则需要使用 ->
显式声明。代码块 {}
和 if
都是表达式,可以在 let
语句右侧使用。
1 2 3 let condition = true ;let number = if condition { 5 } else { 6 };println! ("The value of number is: {number}" );
Rust 有三种循环 loop
、while
和 for
。loop
表示无限循环,可以使用 break
终止并指定返回值。
1 2 3 4 5 6 7 8 9 10 let mut counter = 0 ;let result = loop { counter += 1 ; if counter == 10 { break counter * 2 ; } }; println! ("The result is {result}" );
1 2 3 4 5 for i in 0 ..n { ... }for i in (0 ..n).rev () { ... }for item in &arr { ... }for item in &mut arr { ... }for (i, item) in arr.iter ().enumerate () { ... }
Understanding Ownership
What Is Ownership?
所有权(ownership)是 Rust 用于管理内存的一组规则。栈中的所有数据都必须占用已知且固定的大小,在编译时大小未知或大小可能变化的数据,需要存储在堆上,所有权主要目的就是管理堆数据。所有权规则:① Rust 中的每个值都有所有者(owner);② 每个值在任意时刻有且仅有一个所有者;③ 当所有者离开作用域之后,该值将被丢弃。
1 2 3 4 { let s = String ::from ("hello" ); }
所有权(Ownership)机制让 Rust 无需 GC 就能保证内存安全。GC 是运行时根据可达性分析回收内存,而所有权是在编译时确定变量的作用域,当内存的所有者变量离开作用域之后,相应内存就可以被释放。当变量离开作用域时,Rust 会自动调用 drop
函数来释放内存,类似 C++ 的 RAII 机制。
字符串 String
的数据存储在堆上,字符串变量包含指向堆中数据的指针、数据的长度和容量,其存储在栈上。使用 let s2 = s1;
只会复制引用(浅拷贝),为避免 s1
和 s2
离开作用域之后,释放相同内存两次(调用 drop
函数),该语句执行之后 Rust 会使 s1
无效,不需要在其离开作用域之后回收内存,该操作被称为移动(move)。
1 2 3 let s1 = String ::from ("hello" );let s2 = s1;println! ("{s1}, world!" );
当给已有值的变量赋新值时,Rust 会立即调用 drop
释放原始值的内存。如果想要执行深拷贝,可以使用 clone
函数。如果类型实现 Copy
特征,则旧变量在被赋值给其他变量之后仍然有效。不能为实现 Drop
特征的类型实现 Copy
特征。
1 2 3 let mut s = String ::from ("hello" );s = String ::from ("ahoy" ); println! ("{s}, world!" );
References and Borrowing
引用(reference)类似指针,它是一个地址,但是和指针不同,Rust 保证引用指向某个特定类型的有效值。传递引用值不会转移所有权,所以可以在 main
中继续使用 s1
。函数 calculate_length
中的局部变量 s
是引用类型,其不拥有值的所有权,所以离开作用域之后,其指向的值也不会被回收,所以创建引用的行为被称为借用(borrowing)。
1 2 3 4 5 6 7 8 9 fn main () { let s1 = String ::from ("hello" ); let len = calculate_length (&s1); println! ("The length of '{s1}' is {len}." ); } fn calculate_length (s: &String ) -> usize { s.len () }
默认不能通过引用 &
修改指向的值,除非声明的是可变引用 &mut
,只能对可变变量(使用 let mut
声明)创建可变引用。如果变量有不可变引用,则原变量可读但不可写。如果变量有可变引用,则原变量不可读写。如果变量有一个可变引用,则该变量不能有其他引用,不论是否可变。以上机制可以保证在编译时就避免数据竞争(data race),因为最多有一个线程可以修改变量,而且此时其他线程都无法读取该变量。
1 2 3 4 5 6 7 8 fn main () { let mut s = String ::from ("hello" ); change (&mut s); } fn change (some_string: &mut String ) { some_string.push_str (", world" ); }
引用的作用域从声明的地方开始直到最后一次使用为止,下面的不可变引用 r1
和 r2
的最后一次使用,发生在 r3
的声明之前,所以不会发生编译错误。
1 2 3 4 5 6 7 8 9 let mut s = String ::from ("hello" );let r1 = &s; let r2 = &s; println! ("{r1} and {r2}" );let r3 = &mut s; println! ("{r3}" );
Rust 编译器保证数据不会在其引用之前离开作用域,即不允许悬垂引用,否则会发生编译错误。在其他语言中,C++ 存在悬垂引用,Java 中没有原始指针类型不存在这个问题,Go 中的变量生命周期由可达性决定而不是作用域,通过逃逸分析局部变量会在堆上分配,所以也可以避免悬垂引用。
1 2 3 4 5 6 7 8 fn main () { let reference_to_nothing = dangle (); } fn dangle () -> &String { let s = String ::from ("hello" ); &s }
The Slice Type
切片(slice)是引用类型,不拥有值的所有权。切片仅由指向数据的指针和长度组成,不像 Go 中还包含容量字段。字符串切片的索引必须位于有效的 UTF-8 字符边界上,如果从多字节字符的中间位置创建切片,则会产生运行时错误。
1 2 3 4 let s = String ::from ("hello" );let len = s.len ();let slice = &s[0 ..len];let slice = &s[..];
字符串切片的类型有 &str
和 &mut str
,区别在于指向的数据是否可变。使用 let mut
声明变量,只是表示可以修改变量绑定的切片,和切片数据是否可变无关。字符串字面量的类型是 &str
,所以是不可变的,如果想要获取可变字符串切片,需要使用 String
结合 mut
。
1 let mut s = "Hello, world!" ;
1 2 let mut s = String ::from ("hello" );let slice = &mut s[..];
Defining and Instantiating Structs
在初始化结构体时,如果字段名称和初始化参数相同,则可以省略。如果要使用旧实例的大部分值创建新实例,可以使用结构体更新语法(struct update syntax),..
表示剩余未显式设置的字段和给定旧实例具有相同的值。该语法类似使用 =
进行赋值,所以未实现 Copy
特征的类型会被转移所有权,旧实例的相应字段会失效。
1 2 3 4 5 6 struct User { active: bool , username: String , email: String , sign_in_count: u64 , }
1 2 3 4 5 6 7 8 fn build_user (email: String , username: String ) -> User { User { active: true , username, email, sign_in_count: 1 , } }
1 2 3 4 5 6 7 8 fn main () { let user2 = User { email: String ::from ("another@example.com" ), ..user1 }; }
可以定义元组结构体(tuple struct),元组结构体的字段没有名称只有类型。即使两个元组结构体有相同类型的字段,它们也是不同的类型。可以解构元组结构体,但是需要指定结构体的类型。
1 2 3 4 5 6 7 8 struct Color (i32 , i32 , i32 );struct Point (i32 , i32 , i32 );fn main () { let black = Color (0 , 0 , 0 ); let origin = Point (0 , 0 , 0 ); let Point (x, y, z) = origin; }
没有任何字段的结构体被称为类单元结构体(unit-like struct)。如果在结构体中存储引用类型 &str
,而不是自身拥有所有权的 String
类型,就需要使用生命周期,否则会发生编译错误。
1 2 3 4 5 struct AlwaysEqual ;fn main () { let subject = AlwaysEqual; }
1 2 3 4 5 6 struct User { active: bool , username: &str , email: &str , sign_in_count: u64 , }
Method Syntax
方法的第一个参数必须是 self
,表示调用该方法的结构体实例,&self
实际上是 self: &Self
的缩写。在 impl
块中,Self
类型是 impl
块类型的别名,在下面就是 Rectangle
。方法可以选择不可变借用 &self
、可变借用 &mut self
或获取所有权 self
。方法的名称可以和结构体的字段相同(Java 中也可以,Go 中不行)。当调用方法时,Rust 会自动根据方法签名添加 &
、&mut
或 *
,所以不需要显式转换。
1 2 3 4 5 6 7 8 9 10 struct Rectangle { width: u32 , height: u32 , } impl Rectangle { fn area (&self ) -> u32 { self .width * self .height } }
在 impl
中定义的函数被称为关联函数(associated function),其第一个参数不是 self
(类似 Java 中的静态方法)。调用关联函数需要使用 Rectangle::square(x)
形式,表示函数 square
位于结构体 Rectangle
的命名空间中。每个结构体可以有多个 impl
块,不过必须和结构体在相同的 crate 中。
1 2 3 4 5 6 7 8 impl Rectangle { fn square (size: u32 ) -> Self { Self { width: size, height: size, } } }
Enums and Pattern Matching
Defining an Enum
枚举类型的字段被称为变体(variant),可以将数据附加到变体上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 enum IpAddrKind { V4, V6, } struct IpAddr { kind: IpAddrKind, address: String , } let home = IpAddr { kind: IpAddrKind::V4, address: String ::from ("127.0.0.1" ), }; let loopback = IpAddr { kind: IpAddrKind::V6, address: String ::from ("::1" ), };
1 2 3 4 5 6 7 8 enum IpAddr { V4 (String ), V6 (String ), } let home = IpAddr::V4 (String ::from ("127.0.0.1" ));let loopback = IpAddr::V6 (String ::from ("::1" ));
每个变体附加数据的类型和数量可以不同,可以使用 impl
为枚举类型定义方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 enum Message { Quit, Move { x: i32 , y: i32 }, Write (String ), ChangeColor (i32 , i32 , i32 ), } impl Message { fn call (&self ) { } } let m = Message::Write (String ::from ("hello" ));m.call ();
The match Control Flow Construct
可以使用变量名称之后跟 =>
表示通配模式,放在最后以匹配其他情况,如果不使用变量则可以使用 _ =>
形式。
1 2 3 4 enum Option <T> { None , Some (T), }
1 2 3 4 5 6 7 8 9 10 fn plus_one (x: Option <i32 >) -> Option <i32 > { match x { None => None , Some (i) => Some (i + 1 ), } } let five = Some (5 );let six = plus_one (five);let none = plus_one (None );
Concise Control Flow with if let and let else
1 2 3 4 5 let config_max = Some (3u8 );match config_max { Some (max) => println! ("The maximum is configured to be {max}" ), _ => (), }
1 2 3 4 let config_max = Some (3u8 );if let Some (max) = config_max { println! ("The maximum is configured to be {max}" ); }
1 2 3 4 5 6 7 8 9 10 11 fn describe_state_quarter (coin: Coin) -> Option <String > { let Coin ::Quarter (state) = coin else { return None ; }; if state.existed_in (1900 ) { Some (format! ("{state:?} is pretty old, for America!" )) } else { Some (format! ("{state:?} is relatively new." )) } }
Common Collections
Storing Lists of Values with Vectors
1 2 3 4 5 6 7 8 9 10 let v = vec! [1 , 2 , 3 , 4 , 5 ];let third : &i32 = &v[2 ];println! ("The third element is {third}" );let third : Option <&i32 > = v.get (2 );match third { Some (third) => println! ("The third element is {third}" ), None => println! ("There is no third element." ), }
1 2 3 4 let mut v = vec! [100 , 32 , 57 ];for i in &mut v { *i += 50 ; }
Storing UTF-8 Encoded Text with Strings
Rust 中有两种字符串,字符串切片 str
和字符串 String
,它们使用的都是 UTF-8 编码,String
是对 Vec<u8>
的封装。使用 +
运算符拼接 String
字符串,会获取第一个参数的所有权,以及第二个参数的引用,类似下面的 add
函数。
实际上,&s2
会被转换为 &s2[..]
,从而满足 add
函数的 &str
参数类型,该技术被称为 dref coercion。s3
会获取 s1
的所有权,然后将 s2
复制到其末尾。也可以使用 format!
宏执行字符串拼接,该宏不会获取任何参数的所有权。
1 2 3 let s1 = String ::from ("Hello, " );let s2 = String ::from ("world!" );let s3 = s1 + &s2;
1 fn add (self , s: &str ) -> String {
1 2 3 4 let s1 = String ::from ("tic" );let s2 = String ::from ("tac" );let s3 = String ::from ("toe" );let s = format! ("{s1}-{s2}-{s3}" );
由于字符串使用 UTF-8 编码,所以不支持使用索引访问,字符串的长度是其包含的字节数而不是字符数。在 Go 语言中,可以直接使用索引,访问的就是对应的字节。在 Rust 中,虽然不能使用索引访问字符串,但是可以使用范围获取字符串切片,前提是范围位于有效的 UTF-8 字符边界上。
1 2 let s1 = String ::from ("hi" );let h = s1[0 ];
1 2 let hello = "Здравствуйте" ;let s = &hello[0 ..4 ];
使用 chars()
方法遍历字符,bytes
方法遍历字节。
1 2 3 4 5 6 for c in "Зд" .chars () { println! ("{c}" ); } for b in "Зд" .bytes () { println! ("{b}" ); }
Storing Keys with Associated Values in Hash Maps
在使用 insert
时,对于实现 Copy
特征的类型,其值会复制到哈希表中,而拥有所有权的类型,其值会被移动到哈希表中。如果将引用插入哈希表,则引用指向的值,必须至少和哈希表的生命周期一样长(避免悬垂引用)。哈希表使用 SipHash 哈希函数,可以抵御涉及哈希表的拒绝服务攻击,允许用户指定其他哈希函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 use std::collections::HashMap;let mut scores = HashMap::new ();scores.insert (String ::from ("Blue" ), 10 ); scores.entry (String ::from ("Yellow" )).or_insert (50 ); let team_name = String ::from ("Blue" );let score = scores.get (&team_name).copied ().unwrap_or (0 );for (key, value) in &scores { println! ("{key}: {value}" ); }
Error Handling
Unrecoverable Errors with panic!
panic 表示不可恢复的错误,在遇到 BUG 或显示调用 panic!
宏时产生。当出现 panic 时,程序默认会执行展开(unwinding),这意味着 Rust 会回溯函数栈并清理函数的数据。另一种选择是直接终止(abort),不清理数据就退出程序,此时程序所使用的内存由操作系统清理。
Recoverable Errors with Result
可以使用闭包和 unwrap_or_else
方法替代 match
来简化代码。使用 unwrap
方法,如果 Result
值是 Ok
,则返回其中的值,否则会调用 panic!
。expect
方法类似,只是可以自定义 panic!
的错误信息。
1 2 3 4 enum Result <T, E> { Ok (T), Err (E), }
1 2 3 4 5 6 7 8 9 10 use std::fs::File;fn main () { let greeting_file_result = File::open ("hello.txt" ); let greeting_file = match greeting_file_result { Ok (file) => file, Err (error) => panic! ("Problem opening the file: {error:?}" ), }; }
1 2 3 4 5 6 7 8 use std::fs::File;use std::io::{self , Read};fn read_username_from_file () -> Result <String , io::Error> { let mut username = String ::new (); File::open ("hello.txt" )?.read_to_string (&mut username)?; Ok (username) }
Generic Types, Traits, and Lifetimes
Rust 在编译时对泛型代码单态化(monomorphization)来保证效率,即为使用的具体类型生成对应类型的代码。在其他语言中,C++ 的做法类似,而 Java 执行类型擦除。
1 fn largest <T>(list: &[T]) -> &T {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct Point <T> { x: T, y: T, } impl <T> Point<T> { fn x (&self ) -> &T { &self .x } } impl Point <f32 > { fn distance_from_origin (&self ) -> f32 { (self .x.powi (2 ) + self .y.powi (2 )).sqrt () } } fn main () { let p = Point { x: 5 , y: 10 }; println! ("p.x = {}" , p.x ()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct Point <X1, Y1> { x: X1, y: Y1, } impl <X1, Y1> Point<X1, Y1> { fn mixup <X2, Y2>(self , other: Point<X2, Y2>) -> Point<X1, Y2> { Point { x: self .x, y: other.y, } } } fn main () { let p1 = Point { x: 5 , y: 10.4 }; let p2 = Point { x: "Hello" , y: 'c' }; let p3 = p1.mixup (p2); println! ("p3.x = {}, p3.y = {}" , p3.x, p3.y); }
Traits: Defining Shared Behavior
孤儿规则(orphan rule):只有在特征或类型至少有一个属于当前 crate 时,才能对类型实现特征。也就是说,可以为内部类型实现外部特征、为外部类型实现内部特征,但是不能为外部类型实现外部特征。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 pub trait Summary { fn summarize_author (&self ) -> String ; fn summarize (&self ) -> String { format! ("(Read more from {}...)" , self .summarize_author ()) } } pub struct SocialPost { pub username: String , pub content: String , pub reply: bool , pub repost: bool , } impl Summary for SocialPost { fn summarize_author (&self ) -> String { format! ("@{}" , self .username) } }
1 2 3 pub fn notify (item: &(impl Summary + Display)) {pub fn notify <T: Summary + Display>(item: &T) {
1 2 3 4 5 6 7 fn some_function <T: Display + Clone , U: Clone + Debug >(t: &T, u: &U) -> i32 { fn some_function <T, U>(t: &T, u: &U) -> i32 where T: Display + Clone , U: Clone + Debug , {
Validating References with Lifetimes
Rust 中每个引用都有生命周期(lifetime),通常生命周期可以被隐式地推断出来,否则需要显式写出。生命周期的主要目标是避免悬垂引用,Rust 编译器使用借用检查器(borrow checker)比较作用域,确保所有的借用都是有效的。r
和 x
的生命周期分别被标记为 'a
和 'b
,编译器会发现 r
引用的变量 x
的生命周期比自身更小,这会导致悬垂引用,所以编译器会报错。
1 2 3 4 5 6 7 8 9 10 fn main () { let r ; { let x = 5 ; r = &x; } println! ("r: {r}" ); }
1 2 3 &i32 &'a i32 &'a mut i32
生命周期注解语法用于将函数参数和返回值的生命周期相关联,生命周期也是泛型。以下函数签名表示,泛型生命周期参数 'a
的具体生命周期是 x
和 y
中生命周期的较小者,返回值不能在生命周期 'a
结束之后使用。由于下面的 result
在 y
生命周期结束之后访问,所以会引发编译错误。
1 2 3 fn longest <'a >(x: &'a str , y: &'a str ) -> &'a str { if x.len () > y.len () { x } else { y } }
1 2 3 4 5 6 7 8 9 fn main () { let string1 = String ::from ("long string is long" ); let result ; { let string2 = String ::from ("xyz" ); result = longest (string1.as_str (), string2.as_str ()); } println! ("The longest string is {result}" ); }
结构体可以存储引用类型,不过此时需要使用生命周期注解。下面注解意味着,ImportantExcerpt
实例不能在其字段 part
的生命周期结束之后使用。
1 2 3 struct ImportantExcerpt <'a > { part: &'a str , }
生命周期省略规则(lifetime elision rules):① 为每个引用参数分配不同的生命周期;② 如果只有一个输入生命周期参数,则将其生命周期赋给所有输出生命周期参数;③ 如果输入生命周期参数有 &self
或 &mut self
,则将 self
的生命周期赋给所有输出生命周期参数。如果应用规则之后,仍没有计算出引用的生命周期,则会引发编译错误。
1 2 3 4 5 6 impl <'a > ImportantExcerpt<'a > { fn announce_and_return_part (&self , announcement: &str ) -> &str { println! ("Attention please: {announcement}" ); self .part } }
静态生命周期 'static
表示能够在程序运行期间存活,所有字符串字面量都具有静态生命周期。
1 let s : &'static str = "I have a static lifetime." ;
1 2 3 4 5 6 7 8 9 10 11 12 13 use std::fmt::Display;fn longest_with_an_announcement <'a , T>( x: &'a str , y: &'a str , ann: T, ) -> &'a str where T: Display, { println! ("Announcement! {ann}" ); if x.len () > y.len () { x } else { y } }
Functional Language Features: Iterators and Closures
Closures: Anonymous Functions That Capture Their Environment
闭包(closures)是可以保存在变量中或作为参数传递给其他函数的匿名函数,和函数不同,它允许捕获其被定义时所在作用域中的值。通常不需要为闭包的参数和返回值声明类型注解,编译器可以推断出类型。必须调用 add_one_v3
和 add_one_v4
闭包才能通过编译,编译器会根据调用上下文推断类型,对相同闭包使用不同的类型会引发编译错误。
1 2 3 4 fn add_one_v1 (x: u32 ) -> u32 { x + 1 }let add_one_v2 = |x: u32 | -> u32 { x + 1 };let add_one_v3 = |x| { x + 1 };let add_one_v4 = |x| x + 1 ;
闭包有三种捕获变量的方式:不可变借用、可变借用和获取所有权。前两种方式是隐式推断的,获取所有权使用 move
。
1 2 3 4 5 6 7 8 9 10 use std::thread;fn main () { let list = vec! [1 , 2 , 3 ]; println! ("Before defining closure: {list:?}" ); thread::spawn (move || println! ("From thread: {list:?}" )) .join () .unwrap (); }
闭包可以执行:将捕获的值移出闭包、既不移动也不修改值、
闭包捕获和处理值的方式会影响闭包实现哪些特征:FnOnce
、FnMut
和 Fn
特征分别在获取所有权、可变借用和不可变借用时实现。它们之间是父子关系(和继承无关,只是指定依赖关系),FnOnce <- FnMut <- Fn
。如果闭包不需要从环境中捕获值,可以直接使用函数而不是闭包,例如在 Option<Vec<T>>
值上调用 unwrap_or_else(Vec::new)
。
1 2 3 4 5 6 7 8 9 10 11 impl <T> Option <T> { pub fn unwrap_or_else <F>(self , f: F) -> T where F: FnOnce () -> T { match self { Some (x) => x, None => f (), } } }
1 2 3 4 5 6 7 8 9 impl <T> [T] {pub fn sort_by_key <K, F>(&mut self , mut f: F) where F: FnMut (&T) -> K, K: Ord , { stable_sort (self , |a, b| f (a).lt (&f (b))); } }
Processing a Series of Items with Iterators
iter
方法生成不可变引用的迭代器,iter_mut
生成可变引用的迭代器,into_iter
生成获取所有权的迭代器。
1 2 3 4 5 6 7 pub trait Iterator { type Item ; fn next (&mut self ) -> Option <Self ::Item>; }
1 2 3 let v1 : Vec <i32 > = vec! [1 , 2 , 3 ];let v2 : Vec <_> = v1.iter ().map (|x| x + 1 ).collect ();assert_eq! (v2, vec! [2 , 3 , 4 ]);