函数用于执行一个任务或计算一个值。
函数名称、变量名称使用 snake_case 风格。
每个函数参数都需要标注类型(lifetime 需要使用 ‘_,泛型参数需要使用 _, 来表示由编译器自动推导): fn stash_pop(&self, _env: Arc<HashMap<String, String>>) -> BoxFuture<'_, Result<()>> {
函数使用 fn
声明, 使用 ->
来指定返回值类型,没有指定返回值时默认为 unit type
类型和值 ()
。
函数最多只能有一个返回值,对于多返回值的情况,可以用 tuple
等类型来封装。
函数体最后一个表达式(不以分号结尾)作为函数的返回值, 也可以使用 return
语句提前返回:
// 无参数、无返回值的函数, 无返回值等效于返回 ()
fn greet_world() {
println!("Hello, world!");
}
// 函数只能有一个返回值类型
fn function_name(parameter1: Type1, parameter2: Type2) -> ReturnType {
// ...
}
// 接受一个参数、无返回值的函数
fn greet(name: &str) {
println!("Hello, {}!", name);
}
// 接受两个参数、有返回值的函数
fn add(a: i32, b: i32) -> i32 {
// 表达式的值作为函数的返回值
a + b
}
fn main() {
// 函数定义的顺序是无关的,可以先使用,再定义
fizzbuzz_to(100);
}
fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
if rhs == 0 {
return false;
}
// 返回表达式值
lhs % rhs == 0
}
// 没有指定返回值的函数返回 ()
fn fizzbuzz_to(n: u32) {
for n in 1..=n {
fizzbuzz(n);
}
}
fn fizzbuzz(n: u32) -> () {
if is_divisible_by(n, 15) {
println!("fizzbuzz");
} else if is_divisible_by(n, 3) {
println!("fizz");
} else if is_divisible_by(n, 5) {
println!("buzz");
} else {
println!("{}", n);
}
}
函数支持嵌套定义。在同一个作用域内,函数定义的位置、顺序是无关的,可以先使用再定义:
fn main() {
test();
fn test() {
println!("just fortest");
}
}
函数传参是 Pattern Match
的过程,使用 _
忽略不使用的参数:
/*
FunctionParam :
OuterAttribute* ( FunctionParamPattern | ... | Type)
FunctionParamPattern :
PatternNoTopAlt : ( Type | ... )
*/
fn first((value, _): (i32, i32)) -> i32 { value }
Rust 原生语法不支持可变长度参数 variadic arguments
,但是通过函数宏可以模拟:
macro_rules! sum {
($($x:expr),*) => {
{
let mut total = 0;
$(
total += $x;
)*
total
}
};
}
fn main() {
let result = sum!(1, 2, 3, 4);
println!("{}", result);
}
Rust 仅在 extern "C"
中有限支持 C 风格的可变参数:
extern "C" {
fn printf(format: *const i8, ...) -> i32;
}
fn 函数指针类型 #
fn 函数是一个指针类型,类型为函数签名,如 fn(&City) -> i64
。
- fn 函数指针类型值占用一个机器字 usize,可以和其它类型值一样来使用,如保存到变量,作为函数的参数和返回值等;
- fn 不是闭包,不能捕获上下文中的对象或借用;
// 声明 fn 函数指针类型别名
type Binop = fn(i32, i32) -> i32;
// 下面这些函数签名均和 Binop 一致,故均是 Binop 类型
fn add(a: i32, b: i32) -> i32 { a + b }
fn subtract(a: i32, b: i32) -> i32 { a - b }
fn multiply(a: i32, b: i32) -> i32 { a * b }
// 使用函数指针类型作为参数类型
fn apply_operation(operation: Binop, x: i32, y: i32) -> i32 {
// 调用函数指针对应的函数
operation(x, y)
}
fn main() {
// 保存函数指针类型的变量
let mut operation_to_perform: Binop;
operation_to_perform = add;
println!("Result of add: {}", apply_operation(operation_to_perform, 10, 5));
operation_to_perform = subtract;
println!("Result of subtract: {}", apply_operation(operation_to_perform, 10, 5));
println!("Directly passing multiply: {}", apply_operation(multiply, 10, 5));
}
对于 fn 类型参数,也可以传入没有捕获上下文的闭包函数,Rust 支持通过 type coerce 将闭包类型隐式转换为 fn。
使用 Fn/FnMut/FnOnce 限界的泛型类型,也可以使用 fn 函数;
// 闭包类型参数也可以传入 fn 函数指针
let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
可以给函数整体或函数参数指定 attribute macro
来实现条件编译或特殊处理语义:
fn len(
#[cfg(windows)] slice: &[u16],
#[cfg(not(windows))] slice: &[u8],
) -> usize {
slice.len()
}
函数标准语法 #
https://doc.rust-lang.org/reference/items/functions.html
-
FunctionQualifiers
:fn 前可以加限定符:const,async,unsafe,extern ABI
; -
self 参数支持两种格式:
ShorthandSelf
:(& | & Lifetime)? mut? self
, 如self, mut self, &self, &mut self, &'a mut self
;TypedSelf
:mut? self : Type
, 如self: Type, mut self: Type
, 传入的是 self 或 mut self,会消耗 self;
Function :
FunctionQualifiers fn IDENTIFIER GenericParams? ( FunctionParameters? ) FunctionReturnType? WhereClause? ( BlockExpression | ; )
FunctionQualifiers :
const? async? unsafe? (extern Abi?)?
Abi :
STRING_LITERAL | RAW_STRING_LITERAL
FunctionParameters :
SelfParam ,? | (SelfParam ,)? FunctionParam (, FunctionParam)* ,?
SelfParam :
OuterAttribute* ( ShorthandSelf | TypedSelf )
ShorthandSelf :
(& | & Lifetime)? mut? self
TypedSelf :
mut? self : Type
FunctionParam :
OuterAttribute* ( FunctionParamPattern | ... | Type2 )
FunctionParamPattern :
PatternNoTopAlt : ( Type | ... )
FunctionReturnType :
-> Type
泛型函数是包含泛型参数的函数,格式为 fn my_func<X, Y, Z>
,其中 <X, Y, Z>
为泛型参数列表(Generic parameters
)。
有三种泛型参数类型:lifetime,type,const
lifetime
: 如:'a
,'a: 'b + 'c
,'a: 'static
或'a: '_
type
: 可以用 lifetime 和 trait 进行限界,多个限界用 + 号连接:T
没有任何限界,则默认是Sized
限界;T: 'a + Trait
多个限界用 + 号连接;T = MyType
指定缺省类型;T: 'a + TraitA + for <'a> Fn(&'a i32) -> i32
- 当闭包的参数或返回值使用借用时,需要使用 HRTB 来指定 lifetime。
T: for <'a> Fn(&'a i32) -> i32 + 'b + TraitA
等效于T: (for <'a> Fn(&'a i32) -> i32) + 'b + TraitA
T: 'a + TraitA + std::ops::Index<std::ops::Range<usize>>
const
:const N: usize
,const N: usize = 4
;
GenericParams :
< >
| < (GenericParam ,)* GenericParam ,? >
GenericParam :
OuterAttribute* ( LifetimeParam | TypeParam | ConstParam )
LifetimeParam :
LIFETIME_OR_LABEL ( : LifetimeBounds )?
TypeParam :
IDENTIFIER( : TypeParamBounds? )? ( = Type )?
ConstParam:
const IDENTIFIER : Type ( = Block | IDENTIFIER | -?LITERAL )?
TypeParamBounds :
TypeParamBound ( + TypeParamBound )* +?
TypeParamBound :
Lifetime | TraitBound
TraitBound :
?? ForLifetimes? TypePath // ?? 表示可选的 ?,表示该 trait boud 是可选
| ( ?? ForLifetimes? TypePath ) // 这里的括号为字面量, 不用于分组
LifetimeBounds :
( Lifetime + )* Lifetime?
Lifetime :
LIFETIME_OR_LABEL
| 'static
| '_
ForLifetimes :
for GenericParams
TypePath :
::? TypePathSegment (:: TypePathSegment)* // 支持 ::std::vec::Vec
TypePathSegment :
PathIdentSegment (::? (GenericArgs | TypePathFn))?
TypePathFn :
( TypePathFnInputs? ) (-> Type)?
TypePathFnInputs :
Type (, Type)* ,?
// 其中的 GenericArgs 定义
GenericArgs :
< >
| < ( GenericArg , )* GenericArg ,? >
GenericArg :
Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds
GenericArgsConst :
BlockExpression
| LiteralExpression
| - LiteralExpression
| SimplePathSegment
GenericArgsBinding :
IDENTIFIER GenericArgs? = Type
GenericArgsBounds :
IDENTIFIER GenericArgs? : TypeParamBounds
ForLifetimes 用于 HRTB(Higher-ranked trait bounds)
:
fn call_on_ref_zero<F>(f: F) where for<'a> F: Fn(&'a i32) {
let zero = 0;
f(&zero);
}
fn call_on_ref_zero<F>(f: F) where F: for<'a> Fn(&'a i32) {
let zero = 0;
f(&zero);
}
const 函数 #
用来初始化全局的 const/static
常量,需要在编译时执行,所以实现上有些限制:
- 内部只能调用其它 const 函数;
- 不能动态分配内存,操作原始指针(即使在 unsafe block 中也不行);
- 除了 lifetime 外, 不能使用其他类型作为泛型参数;
extern 函数 #
使用指定的 ABI 来定义函数,常用于将 Rust 函数导出给其它 ABI 的程序,如 C/C++ 程序调用:
- 未指定 extern 时,默认为
extern "Rust"
; - 指定 extern 但是未指定 ABI 时,默认为
extern "C"
fn foo() {}
// 等效于
extern "Rust" fn foo() {}
extern fn new_i32() -> i32 { 0 }
let fptr: extern fn() -> i32 = new_i32;
// 等效于
extern "C" fn new_i32() -> i32 { 0 }
let fptr: extern "C" fn() -> i32 = new_i32;
在使用 extern "C"
将声明函数使用 C ABI 导出时,通常还加 #[no_mangle]
属性,它用于指示 Rust 编译器不对该函数改名,从而让其它语言能正确链接到导出的函数名实
现上。
#[no_mangle]
extern "C" fn new_i32() -> i32 { 0 }
extern block #
类似的还有 extern block
声明,用于生命 Rust 中可以调用的外部函数库中的函数。
// extern block 声明
extern "C" {
fn foo(); // 没有函数体,一般是 FFI 调用外部库的实现。
}
// 需要在 unsafe 中使用 extern block 中生命的对象
unsafe { foo() }
关联函数和方法 #
trait 和类型的 impl XX {}
都支持定义关联函数(Associated functions
)和方法(Method
):
- 第一个参数名为
self
时(如&self,&mut self, self: XX, mut self: XX
)为方法,否则为关联函数。 &self
等效为self: &Self
,&mut self
等效为self: &mut Self
;
方法中可以使用 Self
类型,等效为 impl XX
中的 XX 类型, 当 XX 类型比较复杂(如泛型)时,使用 Self 更简洁;
也可以为方法的 self 参数指定其它类型,如 Box<T>
,这时只能使用该类型的对象来调用方法:
pub fn into_vec<A>(self: Box<[T], A>) -> Vec<T, A> where A: Allocator
let s: Box<[i32]> = Box::new([10, 40, 30]);
let x = s.into_vec();
// s 已经被 move,不能再访问。
函数变量赋值时,输入参数是逆变,返回值是协变:
// 'middle is used in both a co- and contravariant position.
fn takes_fn_ptr<'short, 'middle: 'short>(f: fn(&'middle ()) -> &'middle (), ) {
// As the variance at these positions is computed separately, we can freely shrink 'middle in
// the covariant position and extend it in the contravariant position.
// 函数输入参数是逆变,返回结果是协变。
let _: fn(&'static ()) -> &'short () = f;
}
方法查找 #
t.method()
方法调用时,根据 method 的 self 参数要求,自动对 t 值类型进行转换,如自动解引用和自动借用,type coercion
类型转换等,直到类型匹配该
方法的 self 参数类型。
- 更一般的,
.
操作符支持自动解引用和自动借用。比如sb.field
等效于(*sb).field
即自动解引用后返回 field 字段的对象。
假如:T 类型有一个 foo()
方法,当执行 value.foo()
时,编译器:
- 检查是否可以直接调用
T::foo(value)
,即先看 T 是否直接实现了方法 foo(); - 再尝试调用
<&T>::foo(value)
和<&mut T>::foo(value)
,即看&T, &mut T
类型是否实现了方法 foo(); - 如果 T 不是引用类型, 但是实现了
Deref<Target=U>
, 则执行*T
获得U
类型值, 然后对 U 重新执行 1-2 步骤; - 最后尝试
unsized coercion
到类型 U,然后重新执行 1-2 步骤。
unsized coercion
是 Rust 内置的(不能自定义),目前支持如下三种情况:
11-rust-lang-type-coercion.md
[T; n] to [T].
: 如 array 对象可以调用 slice 的方法。T to dyn U
: 如果 T 实现了U + Sized
,而且 U 是对象安全的(object safe)的 trait;- 实现了
CoerceUnsized<Foo<U>>
的&T,&mut T
和智能指针类型;
let array: Rc<Box<[T; 3]>> = ...;
// val[i] 自动解引用 index() 方法的返回值,等效为 *array.index(0)
let first_entry = array[0];
- 编译器先检查
Rc<Box<[T; 3]>>
类型是否实现了Index trait
,结果没有。同时&Rc<Box<[T; 3]>>
和&mut Rc<Box<[T; 3]>>
也都没有; - 编译器使用
Deref trait
将Rc<Box<[T; 3]>>
到Box<[T; 3]>
,继续尝试; Box<[T; 3]>, &Box<[T; 3]>, 和 &mut Box<[T; 3]>
都没有实现 Index,所以继续Deref
到[T; 3]
;[T; 3] , &[T; 3], &mut [T;3]
没有实现 Index;- 编译器尝试
unsized coercion
,结果为[T]
, 而它实现了Index trait
,所以可以调用index()
函数;
注意:上面的 method lookup 过程不会考虑可变性,lifetime 和 unsafe。
闭包 #
闭包是一种匿名类型,编译器为其自动实现了 Fn/FnMut/FnOnce trait
,可以将它们保存在变量中或作为参数传递给其他函数。
闭包 在定义时(而非调用时) 捕获外围上下文中的对象,这种捕获是闭包内部的行为,不体现在闭包的参数中。
可以不指定闭包的输入参数和返回值类型,由编译器自动推导。作为对比,在定义 fn 函数时,必须指定输入和输出参数类型,且 fn 函数不能捕获外围对象。
闭包特性:
- 使用
||
而非()
来指定输入参数列表; - 如果是单行表达式,可以忽略大括号,否则需要使用大括号;
- 可以省略返回值声明,默认根据表达式自动推导;
- 如果指定返回值类型, 则必须使用大括号;
- 闭包的输入和输出参数一旦被自动推导后就不能变化,后续多次调用时传的或返回的值类型必须相同;
// fn 函数:必须指定输入、输出参数类型
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 ;
// 闭包:返回值类型根据后续对 one 的使用方式来定。
let one = || 1;
// block 中 return 或最后一个表达式值作为返回
let closure_annotated = |i: i32| -> i32 { i + outer_var };
// error:指定返回值类型时必须使用大括号
let is_even = |x: u64| -> bool x % 2 == 0;
// ok
let is_even = |x: u64| -> bool { x % 2 == 0 };
// 单行表达式的结果作为值返回
let closure_inferred = |i | i + outer_var;
let color = String::from("green");
// 不加 move,闭包对应的匿名对象内部共享借用了 color(定义闭包时,借用已经发生)
let print = || println!("`color`: {}", color);
// 使用 move,闭包将 color 捕获到生成的匿名对象内部(定义闭包时,转移已经发生)
let print = move || println!("`color`: {}", color);
// 闭包的输入、输出值类型一旦被推导出来后,就不能再变化。
//
// 两次函数调用的推导类型不一致,编译失败。
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5);
// 传给闭包的参数列表也是 pattern 赋值语法。
// 如果闭包不使用传入的参数,可以将其设置为 _。
fn main() {
foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}
编译器根据闭包使用对象方式来自动确定如何捕获该对象(struct/tuple/enum 是被作为一个整体来捕获,但可以使用临时变量来捕获某个 field):
- 共享借用上下文中的对象 &T: 优先选择该类型,如闭包中以 readonly 方式使用上下文对象;
- 可变借用上下文中的对象 &mut T: 如闭包中修改了上下文对象;
- 将上下文中的对象所有权移动到闭包中(move): 如闭包中 drop 对象,或返回 non-copy 对象 (转移所有权到接收方);
多线程场景的闭包函数在另一个线程中运行,编译器不能推断闭包对象内部借用的对象生命周期是否有效,所以需要确保生成的匿名闭包对象是 'static
的。也即,如果闭包内部借用了上下文中的对象,需要确保
该对象的 lifetime 是 'static
的。一般情况下很难满足这个约束,所以需要使用 move 关键字将上下文对象所有权转移到闭包中。
let s = String::from("coolshell");
// s 作为闭包返回值,所以是 Move 语义。
// 在定义闭包时,s 已经被 move 捕获到闭包中,外部不能再访问。
let take_str = || s;
// s 已经被 move 进闭包,不能再访问。(除非该闭包后续不再使用)
//println!("{}", s);
println!("{}", take_str()); // OK
// 在定义闭包时捕获环境中对象,但如果闭包后续不再使用,则捕获失效,原对象可以继续访问。
let mut count = 0;
let mut inc = || {
count += 1; // &mut 捕获 count 对象
println!("`count`: {}", count);
};
// 错误:count 已经被闭包 &mut 捕获,所以不能再访问和修改。
// count += 2;
inc();
// OK:闭包后续不再使用,故还可以继续访问 count。
assert_eq!(count, 1);
let color = String::from("green");
// print 有效时(后续还有调用)会保有 color 的共享引用
let print = || println!("`color`: {}", color);
print();
// 由于闭包是共享引用,所以原对象还可以有其它共享引用
let _reborrow = &color;
print();
// print 后续不再使用,所以可以 move color
let _color_moved = color;
let movable = Box::new(3);
let consume = || {
println!("`movable`: {:?}", movable);
// drop() 方法需要拥有对象所有权,所以 movable 对象被转移到闭包中
std::mem::drop(movable);
};
consume(); // 该闭包只能调用一次
// consume(); // 报错
闭包的实现 #
闭包实际是编译器自动生成的匿名 struct 类型,编译器自动为该匿名类型实现 Fn/FnMut/FnOnce trait
,具体取决于闭包捕获上下对象的方式:
- 共享捕获:实现了
Fn trait
,可以被调用多次; - 可变捕获:实现了
FnMut trait
,可以被调用多次; - 转移了对象所有权:实现了
FnOnce trait
,只能被调用一次(消耗了匿名对象自身)
// F 是 FnOnce() 类型,所以只能被调用一次;
fn f<F: FnOnce() -> String> (g: F) {
println!("{}", g());
}
let mut s = String::from("foo");
let t = String::from("bar");
// 闭包修改和返回了 s,所以该闭包转移了 s 的所有权,编译器为其实现了 FnOnce trait
f(|| { s += &t; s});
// Prints "foobar".
// 自动为匿名闭包生成的类型大概如下:
struct Closure<'a> {
s : String, // 转移了 s 所有权,不是借用:因为闭包返回 s,所以需要转移 s 所有权。
t : &'a String, // 对于 t 是共享借用
}
// 由于闭包返回 s 即转移了捕获对象的所有权,所以闭包只能调用一次,自动实现 FnOnce trait
impl<'a> FnOnce<()> for Closure<'a> {
type Output = String;
fn call_once(self) -> String {
self.s += &*self.t;
self.s
}
}
// f() 函数调用的效果如下
f(Closure{s: s, t: &t});
闭包内部 drop 捕获的对象,或将捕获对象所有权转移出来或转移给其它函数,则该闭包函数只能调用一次,编译器为它实现 FnOnce trait
:
// 闭包前加 move,无条件转移所引用的对象所有权到闭包中
use std::thread;
fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);
// list 所有权被转移到闭包
thread::spawn(move ||
println!("From thread: {:?}", list)
).join().unwrap();
}
let color = String::from("green");
let print = move || println!("`color`: {}", color);
print();
// 报错:color 已被转移到闭包,不能再访问。
// let _reborrow = &color;
// println!("{}", _reborrow);
// Error
fn main() {
let movable = Box::new(3);
// consume 只能调用一次,因为它内部将 movable 变量 move 走了。
let consume = || {
println!("`movable`: {:?}", movable);
take(movable);
};
consume();
// 错误:闭包捕获的 movable 所有权被转移到 take() 函数,所以闭包只能调用一次。
// consume();
}
fn take<T>(_v: T) {}
对于转移到闭包中的对象,闭包外不能再使用(借用)该变量。外围对象被闭包捕获后,不允许再修改它的值。
- 实际上,一旦对象被借用(不管是共享还是可变借用),只要该借用还有效,对象都不能被修改或 move。《== 借用冻结
fn main() {
let mut a = 123;
let ar = &a;
// a = 456; // Error:cannot assign to `a` because it is borrowed
println!("{ar}")
}
fn main() {
let mut x = 4;
// x 被共享借用
let add_to_x = |y| y + x;
let result = add_to_x(3);
// 输出:The result is 7
println!("The result is {}", result);
// 错误:在被共享借用的有效情况下(后续会调用执行该闭包), 不能修改其值
// x = x + 3;
let result2 = add_to_x(3);
println!("The result2 is {}", result2);
}
闭包类型限界 #
对于使用 Fn/FnMut/FnOnce trait
限界的泛型类型,可以传入闭包,所以闭包可以作为函数返回类型,struct/enum 的成员类型:
Fn/FnMut/FnOnce
作为 trait 限界时,表示该闭包函数的调用方式,如 FnOnce 类型在函数内只能调用一次,而 Fn/FnMut 可以调用多次。
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static, // F 是一个 FnOnce 类型的闭包,同时实现了 Send,具有 'static 生命周期
T: Send + 'static, // 闭包的返回值同样需要实现 Send 和 具有 'static 生命周期
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
let mut acc = 2;
// 传入的闭包实现了 FnMut trait,因为它修改了上下文对象 acc。
foobar(|x| {
acc += 1;
x * acc
});
}
// output: 24
fn foobar<F>(f: F) // 可以对 f 调用多次,因为 F 不含上下文对象的 &mut 借用,也没有转移上下文对象
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
let mut acc = 2;
// 错误:闭包修改了 acc,所以该闭包是 FnMut 类型,不满足函数限界的 Fn 类型要求
foobar(|x| {
acc += 1;
x * acc
});
}
fn foobar<F>(f: F) // f 是 FnOnce 类型,所以可以传入 Fn/FnMut/FnOnce 类似的闭包
where F: FnOnce() -> String
{
println!("{}", f());
// 错误:f 是 FnOnce 类型,所以只能被调用一次。
// println!("{}", f());
}
Fn 是 FnMut 子类型, FnMut 是 FnOnce 子类型, 所以在使用它们进行限界时:
- Fn 最特殊,如果用 Fn 限界,则只能传入 Fn 类型闭包;
- FnOnce 最一般,可以传入所有闭包类型;
// std::ops::FnOnce
pub trait FnOnce<Args> where Args: Tuple,
{
type Output;
// Required method
// 传入 self
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
// std::ops::FnMut,FnMut 是 FnOnce 子类型
pub trait FnMut<Args>: FnOnce<Args> where Args: Tuple,
{
// Required method
// 传入 &mut self
extern "rust-call" fn call_mut( &mut self, args: Args ) -> Self::Output;
}
// std::ops::Fn, Fn 是 FnMut 子类型
pub trait Fn<Args>: FnMut<Args> where Args: Tuple,
{
// Required method
// 传入 &self
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
// F 是 Fn() 类型闭包
fn apply<F>(f: F) where F: Fn() {
f();
}
fn main() {
let x = 7;
let print = || println!("{}", x);
apply(print);
}
impl<T> [T] {
pub fn sort_by_key<K, F>(&mut self, mut f: F)
where
F: FnMut(&T) -> K, // F 是 FnMut 类型,且输入是 &T
K: Ord,
{
stable_sort(self, |a, b| f(a).lt(&f(b)));
}
}
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];
// OK:编译器推断该闭包符合 FnMut 要求,虽然它没有捕获外围任何对象
list.sort_by_key(|r| r.width);
println!("{:#?}", list);
}
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];
let mut sort_operations = vec![];
let value = String::from("by key called");
// 错误:编译器推断该闭包为 FnOnce 类型,不符合 sort_by_key() 的要求 FnMut
list.sort_by_key(|r| {
// 转移了捕获对象 value 的所有权,所以该闭包实现的是 FnOnce
sort_operations.push(value);
r.width
});
println!("{:#?}", list);
}
传入的闭包需要满足 Fn/FnOnce/FnMut
的限界要求,包括输入参数、输出参数的参数类型和个数,如果闭包不使用某个参数,可以设置为 _
:
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));
countdown(3, |_| ());
}
对于用 Fn/FnOnce/FnMut trait
限界的参数,可以传入闭包函数或 fn 函数指针(但 fn 函数指针不支持捕获上下文对象)。
但对于 fn 函数指针类型的函数参数,只能传入函数指针(函数名),而不能传入闭包 ,例外情况是:对于没有捕获上下文对象的闭包,才可以被自动转换为 fn 函数指针类型(隐式自动转换,或使用 as 运算符显式转换):
// 闭包是一种匿名类型,可以赋值被变量
let add = |x, y| x + y;
// 调用闭包函数
let mut x = add(5,7);
// 没有捕获环境中值的闭包,可以转换为函数指针
type Binop = fn(i32, i32) -> i32;
let bo: Binop = add;
x = bo(5,7);
闭包被编译器通过匿名类型来实现,与其它类型一样,也可以自动实现 Send/Sync/Copy/Clone trait
,具体取决于捕获的对象类型。
- 如果闭包的参数、捕获的上下文对象(&T、&mut T、转移所有权)都实现了 Send/Sync,则闭包也实现了 Send/Sync。
- 对于共享借用 &T,如果 T 实现了 Clone 和 Copy,则对只拥有共享引用的闭包,也实现了 Clone 和 Copy;
- 对于可变借用 &mut T 没有实现 Clone 和 Copy,所以对于有可变引用的闭包,没有实现 Clone 和 Copy;
- 对于 move 闭包,如果 move 的所有对象都实现了 Copy 则闭包能 Copy,都能 Clone 则闭包能 Clone;
另外,闭包匿名对象的生命周期取决于通过借用捕获(&T 或 &mut T)的对象的生命周期,如果这些对象都是 ‘static 的(如全局 const 或 static 对象),则闭包也满足 ‘static 要求。
示例:std:🧵:spawn() 函数的闭包和返回值都需要实现 Send 和 ‘static
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static, // 注意:Send + 'static 是对闭包 FnOnce() -> T 的整体要求,而不是对返回值 T 的要求
T: Send + 'static, // 返回值 T 的要求
Fn/FnMut/FnOnce 类型的 trait object #
Fn/FnMut/FnOnce
都是 trait,当作为函数输入参数值或返回值时(而非泛型参数限界),需要使用 trait object
类型,如 &dyn Trait
, Box<dyn Trait>
或 impl Trait
。
// 错误:Fn(i32) -> i32:是 trait 类型,是 unsize 大小,所以不能直接返回
// fn returns_closure() -> Fn(i32) -> i32 {
// |x| x + 1
// }
// 正确:使用 Box 封装的固定大小的 trait object
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
// 正确:使用 impl Trait 对象,在编译时确定的唯一类型
fn create_fn() -> impl Fn() {
let text = "Fn".to_owned(q);
move || println!("This is a: {}", text)
}
fn create_fnmut() -> impl FnMut() {
let text = "FnMut".to_owned();
move || println!("This is a: {}", text)
}
fn create_fnonce() -> impl FnOnce() {
let text = "FnOnce".to_owned();
move || println!("This is a: {}", text)
}
// 问题:C 只能保存一种固定的闭包类型
struct BasicRouter<C> where C: Fn(&Request) -> Response {
routers: HashMap<String, C>;
}
// 解决办法:使用 trait object
type BoxedCallback = Box<dyn Fn(&Request) -> Response>;
struct BasicRouter{
routers: HashMap<String, BoxedCallback>;
}
trait object 默认没有实现 Send、Sync,在多线程、异步场景,需要显式的标记:参考 10-rust-lang-generic-trait.md
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static, // 注意:Send + 'static 是对闭包 FnOnce() -> T 的整体要求,而不是对返回值 T 的要求
T: Send + 'static, // 返回值 T 的要求
闭包的 lifetime 问题 #
闭包作为 trait bound 参数,即函数的参数或返回值时,如果闭包的参数、返回值、捕获的额上下文对象包含借用,则可能会遇到 lifetime 问题。这是由于闭包包含超出作用域的借用时,里面保存的 上下文借用是不安全的。
- 对于 move 闭包,由于拥有上下文对象的所有权,该闭包具有
'static lifetime
。
解决方案:
- 将整个闭包标记为 ‘static,这意味着闭包的参数和闭包内部借用捕获的对象必须都是 ‘static。加
'static
则表示传入的闭包在程序整个生命周期有效,进而里 面的借用也一直有效。
// C 必须加 'static, 否则后续调用 Box::new() 报错。
impl BasicRouter {
fn add_route<C>(&mut self, url: &str, callback: C)
where C: Fn(&Request) -> Response + 'static // 要求闭包是 'static 的,即闭包的参数和捕获的借用都必须是 'static
{
self.routers.insert(url.to_string(), Box::new(callback));
}
}
- 使用 HRTB 来标记闭包的借用参数或返回值的生命周期,这个生命周期任意短,其它任意生命周期都是它的子类型,后续将其它任意生命周期的值赋值给它:
struct Closure<F> {
data: (u8, u16),
func: F,
}
// 1. 函数 Closure 泛型参数中没有 'a lifetitme
//
// 2. 在 F 的 Bound 中使用 for <'a> 来声明 'a lifetime,这里 for <'a> 表示对于任意 lifetime 'a, Fn 都满足, 也即 for <'a> 的
// 'a 可以看作无限短,可以是任何其他 lifetime 的 subtype
impl<F> Closure<F> where F: for <'a> Fn(&'a (u8, u16)) -> &'a u8,
{
fn call<'a>(&'a self) -> &'a u8 {
// 'a 和上面的 for <'a> 没有关系,是 call() 方法自己的 lifetime。
// 由于编译器会自动加 lifetime,所以可以不指定 'a 如:fn call(&self) -> &u8
(self.func)(&self.data)
}
}
fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &data.0 }
fn main() {
'x: {
let clo = Closure {
data: (0, 1),
// 由于 F 是 HRTB,任意其它 lifetime(如 'b) 都是它的子类型,可以赋值给 func。
func: do_it
};
println!("{}", clo.call());
}
}
在定义闭包时(不是将闭包作为 trait bound),如果传入或返回的参数包含引用则可能会有 lifetime 问题:
- 不能为闭包的借用参数和返回值定义 lifetime(但是可以使用外围函数定义的 ’lifetime)。
- 闭包返回值的 lieftime 要比输入参数长,但是由于不能指定 lifetime,不能表达这个语义;
解决办法:
- 使用 nightly toolchain 和开启
#![feature(closure_lifetime_binder)]
,可以为闭包函数指定for <'a>
语法的 lifetime: - 或者,定义一个 helper 函数,可以指定闭包输入、输出参数所需的 lifetime,内部再定义闭包;
- 或者,将闭包转换为 fn 函数指针,函数指针支持使用
for<'a>
来定义高阶函数,https://stackoverflow.com/a/60906558
fn fn_elision(x: &i32) -> &i32 { x } // 函数 OK
let closure_elision = |x: &i32| -> &i32 { x };
// | let closure = |x: &i32| -> &i32 { x };
// | - - ^ returning this value requires that `'1` must outlive `'2`
// | | |
// | | let's call the lifetime of this reference `'2`
// | let's call the lifetime of this reference `'1`
// 解决办法 1 :
fn main() {
// error: lifetime may not live long enough
// let clouse_test = |input: &String| input;
// error: lifetime may not live long enough
// let clouse_test = |input: &String| -> &String {input};
// 闭包不支持定义 'lifetime
// error[E0261]: use of undeclared lifetime name `'a`
// let clouse_test = |input: &'a String| ->&'a String {input};
// 使用 nightly toolchain 和开启 #![feature(closure_lifetime_binder)] 后可以使用 for 定义 'lifetime.
let clouse_test = for <'a> |input: &'a String| ->&'a String {input};
}
// 解决办法 2 : 闭包使用外围 helper 函数定义的 lifetime
fn testStr<'a> (input: &'a String) -> &'a String {
let closure_test = |input: &'a String | -> &'a String {input};
return closure_test(input);
}
// 解决办法 3: 将闭包转换为 fn 函数指针,函数指针支持使用 for<'a> 来定义高阶函数,而且编译期间大小是已知的。
// 但是不能使用 Fn/FnMut/FnOnce 等 trait 类型。
let test_fn: for<'a> fn(&'a _) -> &'a _ = |p: &String| p;
println!("Results:{}", test_fn(&"asdfab".to_string()));
// 其它例子:https://github.com/rust-lang/rust/pull/56746/files
// 下面这些例子,闭包的输入参数 _x 没有使用,所以即使为它指定 'lifetime 也没有影响
fn willy_no_annot<'w>(p: &'w str, q: &str) -> &'w str {
let free_dumb = |_x| { p }; // no type annotation at all
let hello = format!("Hello");
free_dumb(&hello)
}
fn willy_ret_type_annot<'w>(p: &'w str, q: &str) -> &'w str {
// type annotation on the return type
let free_dumb = |_x| -> &str { p };
let hello = format!("Hello");
free_dumb(&hello)
}
fn willy_ret_region_annot<'w>(p: &'w str, q: &str) -> &'w str {
// type+region annotation on return type
let free_dumb = |_x| -> &'w str { p };
let hello = format!("Hello");
free_dumb(&hello)
}
fn willy_arg_type_ret_type_annot<'w>(p: &'w str, q: &str) -> &'w str {
// 如果闭包返回的是 _x, 则会报错。
// type annotation on arg and return types
let free_dumb = |_x: &str| -> &str { p };
let hello = format!("Hello");
free_dumb(&hello)
}
fn willy_arg_type_ret_region_annot<'w>(p: &'w str, q: &str) -> &'w str {
// 如果闭包返回的是 _x, 则会报错。
let free_dumb = |_x: &str| -> &'w str { p }; // fully annotated
let hello = format!("Hello");
free_dumb(&hello)
}
如果要给闭包 move 传参, 比如 clone 后的值或 refer 值, 则建议在一个单独的 block scope 中进行:
// https://rust-unofficial.github.io/patterns/idioms/pass-var-to-closure.html
use std::rc::Rc;
let num1 = Rc::new(1);
let num2 = Rc::new(2);
let num3 = Rc::new(3);
// 建议使用下面的 rebingding 方式
let closure = {
let num2 = num2.clone(); // `num2` is cloned
let num3 = num3.as_ref(); // `num3` is borrowed
move || {
// `num1` is moved
*num1 + *num2 + *num3;
}
};
// 而非下面的形式,因为下面的 num2_cloned/num3_borrowed 的作用域在闭包之外还有效,但是它们已经 move 到闭包了,不能再使用。
use std::rc::Rc;
let num1 = Rc::new(1);
let num2 = Rc::new(2);
let num3 = Rc::new(3);
let num2_cloned = num2.clone();
let num3_borrowed = num3.as_ref();
let closure = move || {
*num1 + *num2_cloned + *num3_borrowed;
};
Diverging functions #
发散函数(Diverging functions) 指的是不返回的函数, 如 loop,panic!() 或 os.exit()
函数:
fn foo() -> ! {
panic!("This call never returns.");
}
fn loop_forever() -> ! {
loop {
println!("I will loop forever!");
}
}
! 也可以作为类型:
#![feature(never_type)]
fn main() {
let x: ! = panic!("This call never returns.");
println!("You will never see this line!");
}
对比: unit type 有唯一值和类型 ():
fn some_fn() {
// 未指定返回值,等效为返回 ();
()
}