本文最后更新于 2025-05-30T17:11:31+08:00
基本输入输出
输入并输出
1 2 3 4 5 6 7 use std::io;fn main () { let mut input = String ::new (); io::stdin ().read_line (&mut input).unwrap (); println! ("input is: {}" , input); }
stdin()
函数获取标准输入的句柄
read_line(buf: &mut String) -> Result<usize>
方法读取一行输入,并将其存储在传入的 String
变量中。成功时,返回读取的字节总数
println!
宏用于打印输出,类似于 C 语言中的 printf
函数。它会自动添加换行符
unwrap() -> T
方法解包 Result
类型,返回 Ok
中的值或引发 panic
在使用 use
语句时一般引入到所用函数或模块的上一级,可以避免命名冲突
读取多行输入
1 2 3 4 5 6 7 8 use std::io;fn main () { let lines = io::stdin ().lines (); for line in lines { println! ("got a line: {}" , line.unwrap ()); } }
lines()
方法返回一个迭代器,迭代器产生的元素末尾不含换行符
格式化输出
format!
宏用于格式化字符串,返回一个 String
print!
宏格式化文本并打印到标准输出
println!
宏格式化文本并打印到标准输出,末尾自动添加换行符
eprint!
宏格式化文本并打印到标准错误输出
eprintln!
宏格式化文本并打印到标准错误输出,末尾自动添加换行符
以上宏的格式化语法全部相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 println! ("Hello, {}!" , "world" );println! ("{1} {0}" , "world" , "hello" );println! ("{0} {} {1} {}" , "hello" , "world" ); println! ("{greeting} {name}" , greeting="hello" , name="world" );let greeting = "hello" ;let name = "world" ;println! ("{greeting} {name}" );
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 27 28 29 30 31 32 33 34 35 println! ("Hello, {:5}!" , "rust" ); println! ("Hello, {1:0$}!" , 5 , "rust" );println! ("Hello, {:width$}!" , "rust" , width=5 );println! ("Hello, {:<6}!" , "rust" ); println! ("Hello, {:^6}!" , "rust" ); println! ("Hello, {:>6}!" , "rust" ); println! ("{:-^12}" , "数字" );println! ("Hello, {:+}!" , 42 ); println! ("Hello, {:#x}!" , 42 ); println! ("Hello, {:#X}!" , 42 ); println! ("Hello, {:#o}!" , 42 ); println! ("Hello, {:#b}!" , 42 ); println! ("Hello, {:#e}!" , 42.0 ); println! ("Hello, {:#E}!" , 42.0 ); println! ("Hello, {:05}!" , 42 ); println! ("Hello, {:.2}!" , 42.12345 ); println! ("Hello, {:.1$}!" , 42.12345 , 2 ); println! ("{{ Hello }}" );#[derive(Debug)] struct Point { x: i32 , y: i32 , }let p = Point { x: 1 , y: 2 };println! ("{:?}" , p); println! ("{:#?}" , p);
Display
trait
要在 println!
中以 {}
的形式输出自定义类型,需要实现 Display
trait。在 fmt
方法中用 write!
宏格式化输出。
1 2 3 4 5 impl fmt ::Display for Point { fn fmt (&self , f: &mut fmt::Formatter) -> fmt::Result { write! (f, "({}, {})" , self .x, self .y) } }
基本类型
有符号整数:i8
、i16
、i32
、i64
、i128
和 isize
(指针宽度)
无符号整数: u8
、u16
、u32
、u64
、u128
和 usize
(指针宽度)
浮点数: f32
、f64
char:单个 Unicode 字符,如 'a','α' 和 ' 锈'
bool:true
或 false
单元类型:()
,空元组
数组:[T; N]
,长度为 N 的数组,元素类型为 T
元组:(T1, T2, ...)
,可以包含不同类型的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let x : i32 = 5 ; let pi : f64 = 3.14 ;let c : char = 'a' ;let w :i32 = pi as i32 ; let y = 5 ; let z = 5u32 ; let f = 0.1 ; let b = true ;let arr : [i32 ; 5 ] = [1 , 2 , 3 , 4 , 5 ]; let zero_arr = [0 ; 5 ]; println! ("First element: {}" , arr[0 ]); println! ("Array length: {}" , arr.len ()); println! ("Slice: {:?}" , &arr[1 ..3 ]); let tup : (i32 , f64 , char ) = (1 , 2.0 , 'a' ); println! ("First element: {}" , tup.0 );
变量绑定
let
用于将值绑定到变量。
绑定默认不可变
使用 mut
关键字使变量可变
1 2 3 4 5 6 7 8 let x = 5 ; let mut y = 10 ; y = 20 ; println! ("x: {}, y: {}" , x, y); let z ; z = 30 ; println! ("z: {}" , z);
变量绑定的作用域限定在一个代码块中
变量可以在同一作用域中被重新绑定,称为遮蔽(shadowing)
1 2 3 4 5 6 7 8 9 10 11 12 fn main () { let mut x = 5 ; { let x = "hello" ; println! ("Inner x: {}" , x); let y = 20 ; } x = 10 ; println! ("Outer x: {}" , x); }
自定义类型
结构体
结构体有三种类型: * 元组结构体:没有字段名,只有字段类型 * 命名结构体:有字段名和字段类型 * 单元结构体:没有字段,只有名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct Unit ; struct Point (i32 , i32 ); #[derive(Debug)] struct Person { name: String , age: u8 , } let p = Point (1 , 2 ); let (x, y) = (3 , 4 );let p = Point (x, y); let p = Point { 0 : 5 , 1 : 6 }; println! ("Point: ({}, {})" , p.0 , p.1 ); let alice = Person { name: String ::from ("Alice" ), age: 22 }; let name = String ::from ("Bob" );let age = 25 ;let bob = Person { name, age }; let charlie = Person { name: String ::from ("Charlie" ), ..person };
枚举
枚举用于定义一个类型,它可以是多个不同的值之一。Rust 中的枚举有点类似于 C 语言中的联合体,但更强大。
1 2 3 4 5 6 7 8 9 10 11 12 13 enum Direction { Up, Down, Left, Right, } enum Action { Move { x: i32 , y: i32 }, Speak (String ), Stop, }
关联函数和方法
结构体和枚举都可以使用 impl
块来实现关联函数或方法
方法是依赖于实例的函数,必须有一个 self
参数。self
是结构体或枚举的实例
关联函数和方法类似于面向对象语言中的静态方法和实例方法
1 2 3 4 5 6 7 8 9 10 11 12 13 impl Person { fn new (name: String , age: u8 ) -> Person { Person { name, age } } fn greet (&self ) { println! ("Hello, my name is {} and I'm {} years old." , self .name, self .age); } fn rename (&mut self , name: String ) { self .name = name; } }
类型别名
类型别名用于给现有类型起一个新的名字。它不会创建新的类型,只是给现有类型起一个别名。类似于 C 语言中的 typedef
。
1 2 3 4 5 type Int = i32 ; type Point = (i32 , i32 ); let x : Int = 5 ; let p : Point = (1 , 2 );
控制流
判断条件不加括号
if
语句
1 2 3 4 5 6 7 8 9 if x > 5 { println! ("x is greater than 5" ); } else if x < 5 { println! ("x is less than 5" ); } else { println! ("x is equal to 5" ); }let positive = if x > 0 { true } else { false };
loop
循环
loop
是无限循环
1 2 3 4 5 6 7 8 9 let cnt = 0 ;loop { println! ("cnt: {}" , cnt); if cnt == 10 { break ; } cnt += 1 ; }
可以从 loop
循环中返回值
1 2 3 4 5 6 7 8 let mut x = 1 ;let result = loop { x = x * 2 if x > 1000 { break x; } };println! ("Result: {}" , result);
while
循环
1 2 3 4 5 let mut x = 1 ;while x < 5 { println! ("x: {}" , x); x += 1 ; }
for
循环
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 27 28 for i in 1 ..5 { println! ("i: {}" , i); }for i in (1 ..5 ).rev () { println! ("i: {}" , i); }let arr = [1 , 2 , 3 , 4 , 5 ];for i in arr { println! ("i: {}" , i); }let mut names = vec! ["Alice" , "Bob" , "Charlie" ];for name in names.iter () { println! ("Name: {}" , name); }for name in names.iter_mut () { *name = "Rust" ; }for name in names.into_iter () { println! ("Name: {}" , name); }
循环标签
Rust 允许在循环前加标签,以便在嵌套循环中使用 break
或 continue
跳出指定的循环
1 2 3 4 5 6 7 8 'outer : for i in 0 ..5 { for j in 0 ..3 { if i == 2 && j == 2 { break 'outer ; } println! ("i: {}, j: {}" , i, j); } }
模式匹配
可以在 match
、if let
、while let
、for
、let
和函数参数中使用模式匹配。
match
语句
match
语句用于模式匹配,可以匹配枚举、整数、字符、字符串等类型。类似于 switch
语句,但更强大
1 2 3 4 5 6 7 8 let x = 5 ;match x { 1 => println! ("One" ), 2 | 3 => println! ("Two or Three" ), 4 ..=10 => println! ("Four to Ten" ), _ => println! ("Other" ), }
1 2 3 4 5 6 7 let x = Some (5 );match x { Some (1 ) => println! ("One" ), Some (n) => println! ("Some {}" , n), _ => println! ("None" ), }
if let
和 while let
if let
在表达式满足模式时执行代码块。它是 match
的简化版本,适用于只关心一个模式的情况。
1 2 3 4 5 6 let x = Some (5 );if let Some (n) = x { println! ("Some {}" , n); } else { println! ("None" ); }
while let
在表达式满足模式时执行循环。
1 2 3 4 5 6 7 8 9 10 let mut x = Some (1 );while let Some (n) = x { if n > 9 { println! ("x is {}, enough" , n); x = None ; } else { println! ("x is {}, not enough" , n); x = Some (n + 1 ); } }
守卫语句和 @
绑定
守卫语句可以在模式匹配时添加额外的 if
条件。
1 2 3 4 5 6 7 8 let x = 5 ;let y = 10 ;match x { 1 => println! ("One" ), n if n < y => println! ("{} is less than {}" , n, y), n if n > y => println! ("{} is greater than {}" , n, y), _ => println! ("Other" ), }
@
绑定可以在测试一个值是否匹配模式的同时将该值绑定到变量上
语法为 variable @ pattern
1 2 3 4 5 6 let x = 5 ;match x { n @ 1 ..=10 => println! ("value in range 1 to 10: {}" , n), _ => println! ("other range" ), }
可以绑定整个模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Point { x: i32 , y: i32 , }let point = Point { x: 10 , y: 20 };match point { p @ Point { x: 0 ..=10 , y } => { println! ("点在 x 轴上 0 到 10 的范围内: {:?}, y = {}" , p, y) }, p @ Point { x, y: 0 ..=10 } => { println! ("点在 y 轴上 0 到 10 的范围内: {:?}, x = {}" , p, x) }, Point { x, y } => println! ("点在其他位置: ({}, {})" , x, y), }
解构
解构元组
1 2 3 4 5 6 7 8 9 10 let point = (1 , 2 );let (x, y) = point; println! ("x: {}, y: {}" , x, y); match point { (0 , 0 ) => println! ("Origin" ), (x, 0 ) => println! ("On the x-axis at {}" , x), (0 , y) => println! ("On the y-axis at {}" , y), (x, y) => println! ("Point at ({}, {})" , x, y), }
解构结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct Point { x: i32 , y: i32 , }let point = Point { x: 1 , y: 2 };let Point { x: i, y: j } = point; let Point { x, y } = point; match point { Point { x: 0 , y: 0 } => println! ("Origin" ), Point { x, y: 0 } => println! ("On the x-axis at {}" , x), Point { x: 0 , y } => println! ("On the y-axis at {}" , y), Point { x, y } => println! ("Point at ({}, {})" , x, y), }
解构枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 enum Action { Move { x: i32 , y: i32 }, Speak (String ), Stop, }let action = Action::Move { x: 1 , y: 2 };match action { Action::Move { x, y } => println! ("Move to ({}, {})" , x, y), Action::Speak (message) => println! ("Speak: {}" , message), Action::Stop => println! ("Stop" ), }
嵌套解构
解构的模式类似于初始化时的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct Point { x: i32 , y: i32 , }struct Circle { center: Point, radius: i32 , }let circle = Circle { center: Point { x: 1 , y: 2 }, radius: 5 , };let Circle { center: Point { x, y }, radius } = circle; println! ("Circle center: ({}, {}), radius: {}" , x, y, radius);
忽略模式
在模式匹配中,可以忽略某些值
使用 _
忽略整个值
下划线 _
是一个通配符,可以匹配任何值但不绑定到变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fn main () { let x = 5 ; let _ = calculate_value (); match x { 1 => println! ("One" ), 2 => println! ("Two" ), _ => println! ("Other" ), } }fn calculate_value () -> i32 { 42 }
使用下划线忽略部分值
可以在模式中使用 _
忽略元组、结构体或者模式的一部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let tuple = (1 , 2 , 3 , 4 );let (first, _, third, _) = tuple;println! ("first: {}, third: {}" , first, third);struct Point { x: i32 , y: i32 , z: i32 , }let point = Point { x: 1 , y: 2 , z: 3 };let Point { x, y: _, z } = point;println! ("x: {}, z: {}" , x, z);fn foo (_: i32 , y: i32 ) { println! ("y: {}" , y); }foo (3 , 4 );
使用 ..
忽略剩余值
双点号 ..
可以用来忽略模式中剩余的部分:
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 27 28 29 30 31 32 let tuple = (1 , 2 , 3 , 4 , 5 );let (first, .., last) = tuple;println! ("first: {}, last: {}" , first, last);struct Point3D { x: i32 , y: i32 , z: i32 , label: String , visible: bool , }let point = Point3D { x: 10 , y: 20 , z: 30 , label: String ::from ("point1" ), visible: true , };let Point3D { x, ..} = point;println! ("x: {}" , x);match point { Point3D { x: 0 , .. } => println! ("On the YZ plane" ), Point3D { x: 10 , y, .. } => println! ("x is 10, y is {}" , y), Point3D { x, y, z, .. } => println! ("Located at ({}, {}, {})" , x, y, z), }
..
必须是无歧义的。例如,(first, .., second, ..)
会导致编译错误,因为无法确定哪些值应该匹配第一个 ..
和哪些值应该匹配第二个 ..
。
使用前缀下划线忽略未使用变量
如果你需要绑定一个变量但不打算使用它,可以给变量名加上下划线前缀来避免未使用变量的警告:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 fn process_data (data: &str ) { let (name, age) = get_user_info (data); println! ("Name: {}" , name); }fn process_data_better (data: &str ) { let (name, _age) = get_user_info (data); println! ("Name: {}" , name); }fn get_user_info (data: &str ) -> (String , u32 ) { (String ::from ("Alice" ), 30 ) }
下划线前缀 _age
与单个下划线 _
的区别在于,_age
仍然会绑定值,而 _
根本不会绑定。
函数和闭包
使用 fn
关键字定义函数
1 2 3 4 5 6 7 8 9 10 11 fn function_name (param1: Type1, param2: Type2) -> ReturnType { if condition { return value; } expression }fn do_nothing () { }
函数可以作为参数,其类型是 fn
1 2 3 4 5 6 7 8 9 fn function (){ println! ("call function" ); }fn call (f:fn ()) { f () }call (function);
闭包 (Closure)
闭包类似其他语言中的 lambda 表达式。它们可以捕获周围的环境。闭包的语法如下:
1 2 3 4 5 6 let closure_name = |param1: Type1, param2: Type2| -> ReturnType { };let add = |x: i32 , y: i32 | x + y; println! ("{}" , add (1 , 2 ));
闭包可以捕获上下文中的变量,闭包有三种捕获方式:
通过引用: &T
通过可变引用: &mut T
通过值: T
1 2 3 4 5 6 7 8 9 10 11 let name = String ::from ("Alice" );let greet = || { println! ("Hello, {}!" , name); };greet (); println! ("Name is still available: {}" , name);
1 2 3 4 5 6 7 8 9 10 11 12 13 let mut counter = 0 ;let mut increment = || { counter += 1 ; println! ("Counter: {}" , counter); };increment (); increment (); println! ("Final counter: {}" , counter);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let name = String ::from ("Bob" );let greet = || { println! ("Hello, {}!" , name); take_ownership (name); }greet (); fn take_ownership (s: String ) { println! ("Goodbye {}!" , s); }
1 2 3 4 5 6 7 8 9 10 11 let name = String ::from ("Charlie" );let greeting = move || { println! ("Goodbye, {}!" , name); };greeting ();
上面三种捕获方式直接影响闭包的 trait 实现:
Fn
: 通过引用捕获,闭包可以多次调用,但在最后一次调用前不能可变借用被捕获的变量
FnMut
: 通过可变引用捕获,闭包可以多次调用,但在最后一次调用前不能再次借用被捕获的变量
FnOnce
: 通过值捕获,闭包只能调用一次,因为它消耗了捕获的变量
三种 trait 的关系:FnOnce
> FnMut
> Fn
。
只要闭包中有变量是通过值捕获的,就实现 FnOnce
;如果没有值捕获,但有可变引用捕获,则实现了 FnMut
;如果只有引用捕获,则实现了 Fn
。
闭包可以作为函数的参数或返回值。在返回闭包时必须使用 move
关键字来捕获变量的所有权,否则在函数退出后产生无效引用。
1 2 3 4 5 6 7 8 fn apply <F>(f: F) where F: FnOnce () { f (); }fn create_closure () -> impl FnOnce () { let name = String ::from ("Dave" ); move || println! ("Hello, {}!" , name) }
高阶函数
高阶函数是指接受一个或多个函数作为参数,并 / 或返回另一个函数的函数。在迭代器中经常使用高阶函数。
1 2 3 4 5 6 7 8 let numbers = vec! [1 , 2 , 3 , 4 , 5 ];let sum_of_squares_of_odds : i32 = numbers.iter () .filter (|&&x| x % 2 != 0 ) .map (|&x| x * x) .sum ();println! ("{}" , sum_of_squares_of_odds);
发散函数
发散函数不会返回,使用 !
标记返回类型。
1 2 3 fn no_return () -> ! { panic! ("This function never returns!" ); }
!
可以被转换为任何类型,这在有些时候很有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fn sum_odd_numbers (up_to: u32 ) -> u32 { let mut acc = 0 ; for i in 0 ..up_to { let addition : u32 = match i%2 == 1 { true => i, false => continue , }; acc += addition; } acc }println! ("Sum of odd numbers up to 9 (excluding): {}" , sum_odd_numbers (9 ));
包、crate 和模块
crate 是 Rust 中的最小编译单元。crate 有两种类型:二进制项和库。二进制项是可执行的程序,库是可以被其他 crate 使用的代码库。
包 (package) 是一个或多个 crate 的集合。包可以包含任意多个二进制项和至多一个库。cargo new
命令就会创建一个包。
main.rs
是与包同名的二进制项,lib.rs
是与包同名的库。其他的二进制项可以在 src/bin
目录下创建。
模块
crate 中可以包含多个模块。模块类似于其他语言中的命名空间,用于组织代码。
模块可以内联在使用它的文件中,也可以在单独的文件中定义。
对于在 main.rs
中声明的 my_module
的模块,编译器会在以下位置查找模块代码:
src/my_module.rs
src/my_module/mod.rs
可以在模块中声明子模块,比如在 my_module.rs
中声明子模块 sub_module
,编译器会在以下位置查找子模块代码:
src/my_module/sub_module.rs
src/my_module/sub_module/mod.rs
1 2 3 4 5 6 7 8 9 10 mod inline_module { pub fn inline_function () { println! ("This is an inline module function" ); } }mod my_module;
1 2 3 4 5 6 7 8 pub fn my_function () {println! ("This is a function in my_module" ); }fn my_private_function () { println! ("This is a private function in my_module" ); }
模块路径
crate
代表当前 crate 的根模块,可以用绝对路径和相对路径两种方法来访问模块。
绝对路径从 crate
开始,使用::
分隔模块名。如 crate::my_module::my_function()
。
相对路径从当前模块开始,使用 super::
访问父模块,使用 self::
访问当前模块,self::
可以省略。
如在 main.rs
中可以使用 my_module::my_function()
来访问 my_module
中的函数。
1 2 3 4 5 6 fn main () { inline_module::inline_function (); my_module::my_function (); }
可见性
模块中的代码默认是私有的,只能在同一模块或子模块中可以访问,可以使用 pub
将代码声明为公共的。
1 2 3 4 5 6 7 8 9 10 11 pub fn my_function () { println! ("This is a function in my_module" ); }fn my_private_function () { println! ("This is a private function in my_module" ); }
使用 pub mod
将一个模块声明为公共的,让除父模块以外的其他地方也能访问该模块。
1 2 3 4 pub fn sub_function () { println! ("This is a function in sub_module" ); }
1 2 3 4 5 6 7 mod my_module; fn main () { }
以上示例中,除非让 sub_module
模块为公共的,否则无法在 main.rs
中访问 sub_function
函数。即便该函数是公共的。
还可以让名称仅在特定路径内可见,如:
如 pub(crate)
,表示该名称在当前 crate 内可见;
pub(super)
表示该名称在父模块内可见;
pub(self)
表示该名称在当前模块内可见;
pub(in path)
表示该名称在指定路径内可见。
use
关键字
可以使用 use
关键字简化模块路径的书写。
在引用函数时路径一般写到函数所在的模块为止,这样可以避免来自不同模块的同名函数冲突。
在引用结构体、枚举等时,一般写出完整路径。
可以使用 as
关键字为引用的模块或函数起别名,但起别名后不能使用原名。
当使用 use
将某个名称导入当前作用域时,该名称就能在当前作用域中使用了,但在此作用域之外仍是私有的。可以使用 pub use
将名称重导出,就好像该名称是在当前作用域中定义的一样。
引入同一模块下的不同名称是可以使用嵌套路径避免太多行。如 use std::io::{self, Write};
还可以用 *
来引入模块中的所有公共项。如 use std::collections::*;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 use my_module::sub_module;use my_module::MyStruct; use my_module::MyEnum as OurEnum; mod my_module;fn main () { sub_module::sub_function (); let my_struct = MyStruct::new (); let my_enum = OurEnum::Variant1; my_module::SubStruct::new (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 pub mod sub_module; pub use sub_module::SubStruct; pub struct MyStruct { pub field1: i32 , pub field2: String , }pub enum MyEnum { Variant1, Variant2, }
1 2 3 4 5 6 7 8 9 pub fn sub_function () { println! ("This is a function in sub_module" ); }pub struct SubStruct { pub field1: i32 , pub field2: String , }
结构体的可见性
结构体的字段默认是私有的,只有在同一模块或子模块中可以访问。可以使用 pub
将结构体的字段声明为公共的。
错误处理
panic
在遇到不可恢复的错误时,Rust 会 panic。
当发生 panic 时,程序默认会 unwinding (回溯栈并清理资源) 然后退出。另一种选择是 abort ,不清理资源直接退出,由操作系统来清理。
要使用 abort 模式,可以在 Cargo.toml 中添加以下配置:
1 2 3 4 5 [profile.debug] panic = "abort" [profile.release] panic = "abort"
abort 模式下生成的二进制文件要更小。
可以使用 panic!
宏来显式触发 panic,并附加错误信息。
1 2 3 fn main () { panic! ("This is a panic message" ); }
另一个类似的宏是 unimplemented!
,可以用于占位符,表示该函数尚未实现。实质上是语义更明确的一种 panic!
。
1 2 3 fn some_function () { unimplemented! ("This function is not implemented yet" ); }
Option
Option
是一个枚举类型,用于表示一个值可能存在或不存在。它有两个变体:
Some(T)
:表示存在一个值
None
:表示不存在值
在使用 Option
时,可以使用模式匹配或用 unwrap
方法来处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fn main () { let some_value : Option <i32 > = Some (5 ); let none_value : Option <i32 > = None ; match some_value { Some (v) => println! ("Value is: {}" , v), None => println! ("No value" ), } let value = some_value.unwrap (); println! ("Unwrapped value: {}" , value); let default_value = none_value.unwrap_or (0 ); println! ("Default value: {}" , default_value); }
可以用 ?
运算符解开 Option
,相比于模式匹配更简洁。x
是一个 Option
变量,当 x
为 Some
时,x?
会返回 x
的值;当 x
为 None
时,函数会终止并返回 None
。
1 2 3 4 fn add_one (x: Option <i32 >) -> Option <i32 > { let value = x?; Some (value + 1 ) }
一些常用的方法:
is_some
和 is_none
:检查是否有值,返回布尔值
unwrap
:提取值,如果没有值则 panic
unwrap_or
:提取值,如果没有值则返回提供的默认值
unwrap_or_else
:提取值,如果没有值则调用提供的闭包
map
: 将值从一种类型转换为另一种类型,如果没有值则返回 None
Some
和 None
可分别看作是 true
和 false
,因此有以下方法:
and
:如果 self
是 None
,返回 None
,否则返回另一个 Option
or
:如果 self
是 Some
,返回 self
,否则返回另一个 Option
and_then
:如果 self
是 Some
,则调用闭包并返回其结果,否则返回 None
(处理有值的情况)
or_else
:如果 self
是 None
,则调用闭包并返回其结果,否则返回 self
(处理无值的情况)
Result
Result
是一个枚举类型,用于表示一个操作的结果。它有两个变体:
Ok(T)
:表示操作成功,并包含一个值
Err(E)
:表示操作失败,并包含一个错误值
Result
有许多和 Option
类似的方法,如 unwrap
、expect
、map
、and_then
等。
1 2 3 4 5 fn add (x: &str , y: &str ) -> i32 { let first = x.parse::<i32 >().unwrap_or (0 ); let second = y.parse::<i32 >().unwrap_or (0 ); first + second }
同样可以用 ?
运算符将 Result
解开或向上传播错误。x
是一个 Result
变量,当 x
为 Ok
时,x?
会返回 x
的值;当 x
为 Err
时,函数会返回该错误。
泛型
语法
使用泛型时,在名称后面加上尖括号 <>
,并在尖括号中指定类型参数。注意在实现方法时 impl
块后也要加上泛型参数。
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 27 28 29 30 31 32 fn generic_function <T>(arg: T) -> T { arg }struct GenericStruct <T> { field: T, }enum GenericEnum <T> { Variant (T), }impl <T> GenericStruct<T> { fn new (value: T) -> Self { GenericStruct { field: value } } fn get_field (&self ) -> &T { &self .field } }impl GenericStruct <i32 > { fn add (&self , other: &GenericStruct<i32 >) -> i32 { self .field + other.field } }
约束
可以在泛型参数后面添加约束,限制泛型参数的类型。约束使用 where
关键字或在尖括号中指定。
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 27 28 29 30 fn print_value <T: std::fmt::Display>(value: T) { println! ("{}" , value); }fn print_value_where <T>(value: T) where T: std::fmt::Display { println! ("{}" , value); }fn print_value_multiple <T: Debug + Display>(value: T) { println! ("{:?}" , value); }fn print_value_multiple_where <T>(value: T) where T: Debug + Display, { println! ("{:?}" , value); }fn print_option <T>(value: Option <T>) where Option <T>: Debug , { println! ("{:?}" , value); }
trait
trait 类似于其他语言中的接口,定义了一组方法的签名。实现 trait 的类型必须实现这些方法。trait 中的方法可以有默认实现。
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 trait Shape { fn area (&self ) -> f64 ; fn perimeter (&self ) -> f64 ; fn description (&self ) -> String { format! ("Area: {}, Perimeter: {}" , self .area (), self .perimeter ()) } }struct Circle { radius: f64 , }struct Rectangle { width: f64 , height: f64 , }impl Shape for Circle { fn area (&self ) -> f64 { std::f64 ::consts::PI * self .radius * self .radius } fn perimeter (&self ) -> f64 { 2.0 * std::f64 ::consts::PI * self .radius } }impl Shape for Rectangle { fn area (&self ) -> f64 { self .width * self .height } fn perimeter (&self ) -> f64 { 2.0 * (self .width + self .height) } }fn print_shape_info <T: Shape>(shape: &T) { println! ("{}" , shape.description ()); }fn print_shape_info2 (shape: &impl Shape ) { println! ("{}" , shape.description ()); }fn create_rectangle (width: f64 , height: f64 ) -> impl Shape { Rectangle { width, height } }fn create_shape (shape_type: &str ) -> Box <dyn Shape> { match shape_type { "circle" => Circle { radius: 1.0 }, "rectangle" => Rectangle { width: 1.0 , height: 2.0 }, _ => panic! ("Unknown shape type" ), } }
关联类型
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 27 28 29 30 31 32 33 34 35 trait GenericTrait <A, B> {}fn func1 <A, B, T>(t: T) where T: GenericTrait<A, B> { }impl GenericTrait <i32 , i32 > for MyStruct { }impl GenericTrait <i32 , f64 > for MyStruct { }trait AssociatedTrait { type A ; type B ; }fn func2 <T: AssociatedTrait>(t: T) { }impl AssociatedTrait for MyStruct { type A = i32 ; type B = i32 ; }
泛型 trait 适用于一个 trait 对不同类型有不同实现的情况,例如类型转换的 From<T>
trait
关联类型 trait 适用于一个类型只能有一种 trait 实现的情况,例如 Iterator
trait
默认泛型参数和运算符重载
可以为 trait 定义默认的泛型参数,这样在实现 trait 时可以省略类型参数。
一个例子是运算符相关的 trait,可以为类型实现这些 trait 来重载运算符。
Add
trait 的泛型参数默认是 Self
,表示与实现该 trait 的类型相同。
1 2 3 trait Add <Rhs = Self > { fn add (self , rhs: Rhs) -> Self ; }
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 27 28 29 30 31 use std::ops::Add;struct Meter (i32 );struct Kilometer (i32 );impl Add for Meter { type Output = Meter; fn add (self , rhs: Meter) -> Meter { Meter (self .0 + rhs.0 ) } }impl Add <Kilometer> for Meter { type Output = Meter; fn add (self , rhs: Kilometer) -> Meter { Meter (self .0 + rhs.0 * 1000 ) } }fn main () { let m1 = Meter (100 ); let m2 = Meter (200 ); let km1 = Kilometer (1 ); let total_meters = m1 + m2 + km1; println! ("Total meters: {}" , total_meters.0 ); }
不同 trait 中的同名方法
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 trait Business { fn run (&self ); fn exit (); }trait Program { fn run (&self ); fn exit (); }struct Robot ;impl Robot { fn run (&self ) { println! ("Moving quickly" ); } fn exit () { println! ("Moving out" ); } }impl Business for Robot { fn run (&self ) { println! ("Managing business" ); } fn exit () { println! ("Quit business" ); } }impl Program for Robot { fn run (&self ) { println! ("Starting program" ); } fn exit () { println! ("Stop program" ); } }fn main () { let robot = Robot; robot.run (); Business::run (&robot); Program::run (&robot); Robot::exit (); <Robot as Business>::exit (); <Robot as Program>::exit (); }
超 trait
一个 trait 可以依赖于另一个 trait(超 trait)。这允许在实现 trait 时使用超 trait 的方法,类似于继承。
1 2 3 trait Human {}trait Student : Human {}
所有权和生命周期
所有权规则
Rust 的所有权系统遵循三个基本规则:
每个值都有一个所有者(变量)
值在任意时刻只能有一个所有者
当所有者离开作用域时,值会被丢弃
1 2 3 4 5 6 { let s1 = String ::from ("hello" ); let s2 = s1; println! ("{}" , s2); }
所有权转移
当涉及堆数据时,赋值操作会导致所有权转移:
1 2 3 4 let s1 = String ::from ("hello" ); let s2 = s1;
实现了 Copy
trait 的类型不会发生所有权转移,而是进行复制。以下类型默认实现了 Copy
trait:
所有整数类型,如 u32
布尔类型,bool
浮点数类型,如 f64
字符类型,char
仅包含以上类型的元组,如 (i32, i32)
1 2 3 let x = 5 ;let y = x; println! ("x = {}, y = {}" , x, y);
克隆(Clone)
如果需要深度复制堆数据而不是转移所有权,可以使用 clone
方法
1 2 3 let s1 = String ::from ("hello" );let s2 = s1.clone (); println! ("s1 = {}, s2 = {}" , s1, s2);
函数与所有权
函数参数和返回值也遵循所有权规则
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 fn main () { let s1 = String ::from ("hello" ); take_ownership (s1); let s2 = gives_ownership (); println! ("{}" , s2); let s3 = String ::from ("world" ); let s4 = takes_and_gives_back (s3); println! ("{}" , s4); }fn take_ownership (some_string: String ) { println! ("{}" , some_string); } fn gives_ownership () -> String { let some_string = String ::from ("yours" ); some_string }fn takes_and_gives_back (a_string: String ) -> String { a_string }
引用和借用
为了避免每次传递值都转移所有权,Rust 提供了 "引用" 的概念,允许使用值但不获取其所有权。
引用基础
创建引用的行为称为 "借用"
1 2 3 4 5 6 7 8 9 fn main () { let s1 = String ::from ("hello" ); let len = calculate_length (&s1); println! ("'{}' 的长度是 {}." , s1, len); }fn calculate_length (s: &String ) -> usize { s.len () }
引用的示意
1 2 3 4 s1 -----> String("hello" ) ^ | s
可变引用
引用默认是不可变的。如果想修改借用的值,需要使用可变引用
1 2 3 4 5 6 7 8 9 fn main () { let mut s = String ::from ("hello" ); change (&mut s); println! ("{}" , s); }fn change (some_string: &mut String ) { some_string.push_str (", world" ); }
引用规则
引用有两条核心规则:
在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用
引用必须始终有效(不能指向已释放的内存)
这些规则在编译时防止数据竞争(data race)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let mut s = String ::from ("hello" );let r1 = &s; let r2 = &s; println! ("{} and {}" , r1, r2);let r3 = &mut s; println! ("{}" , r3);let mut s = String ::from ("hello" );let r1 = &mut s;
悬垂引用
Rust 编译器确保引用永远不会成为悬垂引用(指向已释放的内存)
1 2 3 4 5 6 7 8 fn main () { let reference_to_nothing = dangle (); }fn dangle () -> &String { let s = String ::from ("hello" ); &s }
解决方法是直接返回 String
1 2 3 4 fn no_dangle () -> String { let s = String ::from ("hello" ); s }
解引用与自动解引用
使用 *
操作符可以访问引用指向的值
1 2 3 4 let x = 5 ;let y = &x;assert_eq! (5 , x);assert_eq! (5 , *y);
Rust 在许多情况下会自动解引用,特别是
方法调用:object_ref.method()
字段访问:object_ref.field
比较操作:ref1 == ref2
与模式匹配结合使用:match *ref_val { ... }
1 2 3 4 5 6 7 8 9 10 struct Point { x: i32 , y: i32 , }let point = Point { x: 1 , y: 2 };let point_ref = &point;println! ("坐标: ({}, {})" , point_ref.x, point_ref.y);
切片(Slice)
切片是对集合中部分连续元素的引用,不拥有所有权
1 2 3 4 let s = String ::from ("hello world" );let hello = &s[0 ..5 ]; let world = &s[6 ..11 ]; let whole = &s[..];
字符串字面量是字符串切片
1 let s : &str = "Hello, world!" ;
函数参数应优先使用切片类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fn first_word (s: &str ) -> &str { let bytes = s.as_bytes (); for (i, &item) in bytes.iter ().enumerate () { if item == b' ' { return &s[0 ..i]; } } &s[..] }let my_string = String ::from ("hello world" );let word = first_word (&my_string[..]); let my_literal = "hello world" ;let word = first_word (my_literal);
数组切片
1 2 let a = [1 , 2 , 3 , 4 , 5 ];let slice = &a[1 ..3 ];
生命周期
生命周期是 Rust 另一个独特的概念,用于确保引用在使用时始终有效。
生命周期的概念
生命周期是引用有效的范围。大多数情况下,生命周期是隐含的,但有时需要显式标注
1 2 3 4 5 6 7 8 9 fn main () { let r ; { let x = 5 ; r = &x; } println! ("r: {}" , r); }
生命周期标注语法
生命周期标注以撇号('
)开头,通常使用小写字母如 'a
1 2 3 &i32 &'a i32 &'a mut i32
函数中的生命周期标注
当函数有多个引用参数并返回引用时,编译器可能无法推断生命周期之间的关系
1 2 3 4 5 6 7 8 fn longest (x: &str , y: &str ) -> &str { if x.len () > y.len () { x } else { y } }
使用生命周期标注,明确参数和返回值的关系
1 2 3 4 5 6 7 fn longest <'a >(x: &'a str , y: &'a str ) -> &'a str { if x.len () > y.len () { x } else { y } }
这表示 x 和 y 至少与返回的引用存活一样长。
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fn main () { let string1 = String ::from ("abcd" ); let string2 = "xyz" ; let result = longest (string1.as_str (), string2); println! ("最长的字符串是: {}" , result); let string1 = String ::from ("long string is long" ); let result ; { let string2 = String ::from ("xyz" ); result = longest (string1.as_str (), string2.as_str ()); } }
生命周期省略规则
Rust 有三条生命周期省略规则,让代码更简洁:
每个引用参数都有自己的生命周期参数
如果只有一个输入生命周期参数,它会被赋给所有输出生命周期参数
如果有多个输入生命周期参数,但其中一个是 &self
或 &mut self
,则 self 的生命周期被赋给所有输出生命周期参数
例如,以下两个函数签名是等价的
1 2 fn first_word (s: &str ) -> &str { }fn first_word <'a >(s: &'a str ) -> &'a str { }
结构体中的生命周期
当结构体持有引用时,需要标注生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct ImportantExcerpt <'a > { part: &'a str , }fn main () { let novel = String ::from ("Call me Ishmael. Some years ago..." ); let first_sentence = novel.split ('.' ).next ().expect ("找不到句号" ); let excerpt = ImportantExcerpt { part: first_sentence, }; println! ("{}" , excerpt.part); }
静态生命周期
'static
生命周期表示引用在整个程序运行期间都有效。所有字符串字面量都有 'static
生命周期
1 let s : &'static str = "我有静态生命周期" ;
生命周期约束与泛型
生命周期可以与泛型和 trait 约束结合使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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! ("公告:{}" , ann); if x.len () > y.len () { x } else { y } }
宏
测试
迭代器
迭代器的核心是 Iterator
trait,它定义了迭代器的基本行为。
1 2 3 4 trait Iterator { type Item ; fn next (&mut self ) -> Option <Self ::Item>; }
迭代器有三种类型: * Iter
:对集合的不可变引用进行迭代。在集合上调用 iter
方法返回 * IterMut
:对集合的可变引用进行迭代,可以修改集合中的元素。在集合上调用 iter_mut
方法返回 * IntoIter
:拥有集合的迭代器,会消耗集合中的元素。在集合上调用 into_iter
方法返回
实现迭代器只需指定 Item
关联类型,并实现 next
方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct MyList { data: Vec <i32 >, index: usize , }impl Iterator for MyList { type Item = i32 ; fn next (&mut self ) -> Option <Self ::Item> { if self .index < self .data.len () { let item = self .data[self .index]; self .index += 1 ; Some (item) } else { None } } }
迭代器的强大之处在于其提供了许多有用的方法,这些方法可以链式调用,从而实现强大的功能。
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 let numbers = vec! [1 , 2 , 3 , 4 , 5 ];let mut iter = numbers.iter ();assert_eq! (iter.next (), Some (&1 )); assert_eq! (iter.next (), Some (&2 )); assert_eq! (numbers.iter ().last (), Some (&5 ));assert_eq! (numbers.iter ().nth (2 ), Some (&3 )); let first_two : Vec <_> = numbers.iter ().take (2 ).collect ();assert_eq! (first_two, vec! [&1 , &2 ]);assert_eq! (numbers.iter ().count (), 5 );let doubled : Vec <_> = numbers.iter ().map (|&x| x * 2 ).collect ();assert_eq! (doubled, vec! [2 , 4 , 6 , 8 , 10 ]);let even : Vec <_> = numbers.iter ().filter (|&&x| x % 2 == 0 ).collect ();assert_eq! (even, vec! [&2 , &4 ]);let factorial : i32 = numbers.iter ().fold (1 , |acc, &x| acc * x);assert_eq! (factorial, 120 ); let sum : i32 = numbers.iter ().sum ();assert_eq! (sum, 15 );let more_numbers = vec! [6 , 7 , 8 ];let combined : Vec <_> = numbers.iter ().chain (more_numbers.iter ()).collect ();assert_eq! (combined, vec! [&1 , &2 , &3 , &4 , &5 , &6 , &7 , &8 ]);let indexed : Vec <_> = numbers.iter ().enumerate ().collect ();assert_eq! (indexed, vec! [(0 , &1 ), (1 , &2 ), (2 , &3 ), (3 , &4 ), (4 , &5 )]); numbers.iter ().for_each(|&x| println! ("{}" , x)); let reversed : Vec <_> = numbers.iter ().rev ().collect ();assert_eq! (reversed, vec! [&5 , &4 , &3 , &2 , &1 ]);let all_positive = numbers.iter ().all (|&x| x > 0 );assert! (all_positive); let any_greater_than_three = numbers.iter ().any (|&x| x > 3 );assert! (any_greater_than_three);
collect()
将迭代器转换为集合类型,具体类型取决于上下文。可以用 collect::<type>()
指定类型。
1 2 3 4 5 6 7 8 9 10 11 12 let numbers = vec! [1 , 2 , 3 , 4 , 5 ];let doubled : Vec <i32 > = numbers.iter ().map (|&x| x * 2 ).collect (); let words = vec! [String ::from ("hello" ), String ::from (" " ), String ::from ("world" )]; let string : String = chars.iter ().map (|s| s.to_uppercase ()).collect (); let char_list : Vec <String > = chars.iter ().map (|s| s.to_uppercase ()).collect (); let results = [Ok (1 ), Ok (2 ), Ok (3 )];let result_with_list : Result <Vec <i32 >, String > = results.iter ().cloned ().collect (); let list_of_results : Vec <Result <i32 , String >> = results.iter ().cloned ().collect ();