跳过正文

9. 函数、方法和闭包:function/method/closure

·
Rust
目录
rust-lang - 这篇文章属于一个选集。
§ 9: 本文

函数用于执行一个任务或计算一个值。函数定义顺序没有关系, 且可以先使用再定义:

// 无参数、无返回值的函数, 无返回值等效于返回 ()
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 声明, 使用 -> 来指定返回值类型,没有指定的默认为 unit type (), 最多只能有一个返回值。函数体最后一个表达式(不以分号结尾)作为函数的返回值, 也可以使用 return 语句提前返回:

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 }

最后一个参数类型可以是 ..., 表示可变参数函数 variadic function:

fn variadic_fn(input: int32, input2: ...) -> i32 { value }

fn 函数是一个指针类型,类型和函数签名一致,如 fn(&City) -> i64

函数指针类型占用一个机器字 usize,可以当作值来使用,如保存到变量,作为函数的参数和返回值等:

let my_key_fn: fn(&City) -> i64 = if xxx {my_func_1} else {my_func_2};

cities.sort_by_key(my_key_fn);

fn count_selected_cities(cities: &Vec<City>, test_fn: fn(&City) -> bool) -> usize {}

可以给函数整体或函数参数指定 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 参数支持两种格式:
    1. ShorthandSelf : (& | & Lifetime)? mut? self, 如 self, mut self, &self, &mut self, &'a mut self;
    2. 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

  1. lifetime: 如:'a, 'a: 'b + 'c, 'a: 'static'a: '_
  2. type: 可以用 lifetime 和 trait 进行限界
    • T 没有任何限界,则默认是 Sized 限界;
    • T: 'a + Trait 多个限界用 + 号连接;
    • T = MyType 指定缺省类型;
    • T: 'a + TraitA + for <'a> Fn(&'a i32) -> i32
    • Fn/FnMut/FnOnce 也是闭包,也可以用于限界。但参数或返回值使用借用时,需要使用 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>>
  3. 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 常量,实现限制:

  1. 内部只能调用其它 const 函数;
  2. 不能分配内存和操作原始指针(即使在 unsafe block 中也不行);
  3. 除了 lifetime 外, 不能使用其他类型作为泛型参数;

extern 函数:使用指定的 ABI 来定义函数,常用于将 Rust 函数导出给其它 ABI 的程序调用

  • 未指定 extern 时,默认为 extern "Rust";
  • 指定 extern 但是未指定 ABI 时,默认为 extern "C", 这时一版还要加 #[no_mangle] 属性,它用于指示 Rust 编译器不要对该函数改名,从而让其它语言能正确识别该 Rust 函数;
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;

// 声明函数使用 C ABI,这样可以导出给 C 程序调用
#[no_mangle]
extern "C" fn new_i32() -> i32 { 0 }

类似的还有 extern block,用于在 Rust 中调用其它语言库的函数。

// extern block 声明
extern "C" {
  fn foo(); // 没有函数体,一般是 FFI 调用外部库的实现
}
unsafe { foo() }

impl block 中定义类型的关联函数(Associated functions)和方法(Method):

  • 第一个参数名为 self 时(如 &self,&mut self)是方法,否则为关联函数。&self 等效为 &Self,&mut self 等效为 &mut Self;
  • Self 等效为 impl XX 中的 XX 类型, 当 XX 类型比较复杂(如泛型)时,使用 Self 更简洁;

方法的 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;
}

method lookup
#

t.method() 方法调用时,根据 method 的 self 参数要求,自动对 t 值类型进行转换,如自动解引用和自动借用,type coercion 等,直到类型匹配等方法的 self 参数。

假如 T 类型有一个 foo() 方法,当执行 value.foo() 时,编译器:

  1. 检查是否可以直接调用 T::foo(value),即先看 T 是否直接实现了方法 foo();
  2. 再尝试调用 <&T>::foo(value)<&mut T>::foo(value) ,即看 &T, &mut T 类型是否实现了方法 foo();
  3. 如果 T 不是引用类型, 但是实现了 Deref<Target=U>, 则执行 *T 获得 U 类型值, 然后对 U 重新执行 1-2 步骤;
  4. 最后尝试 unsized coercion 到类型 U,然后重新执行 1-2 步骤。

Rust 目前支持的 unsized coercion:

  1. [T; n] to [T]. : 如 array 对象可以调用 slice 的方法。
  2. T to dyn U: 如果 T 实现了 U + Sized,而且 U 是对象安全的(object safe)的 trait;
  3. 实现了 CoerceUnsized<Foo<U>>&T,&mut T 和智能指针类型;
let array: Rc<Box<[T; 3]>> = ...;
// val[i] 自动解引用 index() 方法的返回值,等效为 *array.index(0)
let first_entry = array[0];
  1. 编译器先检查 Rc<Box<[T; 3]>> 类型是否实现了 Index,结果没有。同时 &Rc<Box<[T; 3]>>&mut Rc<Box<[T; 3]>> 也都没有;
  2. 编译器使用 Deref 将 Rc<Box<[T; 3]>> 到 Box<[T; 3]> ,继续尝试;
  3. Box<[T; 3]>, &Box<[T; 3]>, 和 &mut Box<[T; 3]> 都没有实现 Index,所以继续 Deref 到 [T; 3];
  4. [T; 3] , &[T; 3], &mut [T;3] 没有实现 Index;
  5. 编译器尝试 unsized coercion 它,结果为 [T], 而它实现了 Index,所以可以调用 index() 函数;

注意:上面的 method lookup 过程不会考虑可变性,lifetime 和 unsafe。

closure
#

闭包是一种匿名类型,编译器为其自动实现了 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  ;

// 闭包:返回值类型自动推导为 i32
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;

// 使用 move,闭包可以捕获上下文对象的所有权
let color = String::from("green");
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):

  1. Imut Refer:不可变引用外围环境中的对象,优先选择该类型;
  2. Mut Refer:可变引用外围环境中的对象;
  3. Move:将外围对象的所有权移动到闭包函数中,如:闭包内 drop 对象,或返回 non-copy 对象 (转移所有权到接收方);
    • 多线程场景:多线程场景的闭包函数在另一个线程中运行,编译器不能推断闭包引用对象的生命周期是否有效,所以需要 move,这样确保闭包捕获的对象是一直有效的;
    • 使用 move 后,闭包捕获了上下文对象,这些对象的生命周期完全由闭包内部的逻辑来控制,所以该闭包实现了 'static ;
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 类型,编译器根据闭包中使用环境对象的方式来确定它的捕获方式:ref(优先),mut ref 或 move,同时为该匿名类型实现 Fn/FnMut/FnOnce trait

fn f<F: FnOnce() -> String> (g: F) {
    println!("{}", g());
}
let mut s = String::from("foo");
let t = String::from("bar");
f(|| { s += &t; s});
// Prints "foobar".

// 自动为匿名闭包生成的类型大概如下:
struct Closure<'a> {
    s : String, // 捕获
    t : &'a String, // 借用
}

// 由于闭包返回 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, 具体实现哪一种类型,取决于闭包的实现:

  1. FnOnce 类型:只能调用一次(消耗掉 self),该闭包接管了外界环境中的值;
  2. FnMut 类型:可以调用多次,该闭包使用 &mut 来捕获外界值;
  3. Fn 类型:可以调用多次,该闭包使用 &T 来捕获外界值;

Fn 是 FnMut 子类型, FnMut 是 FnOnce 子类型, 所以:

  1. Fn 最特殊,如果用 Fn 限界,则只能传入 Fn 类型闭包;
  2. FnOnce 最一般,可以传入所有闭包类型;

对于使用 Fn/FnMut/FnOnce trait 限界的泛型类型,可以传入闭包,所以闭包可以作为函数返回类型,struct/enum 的成员类型:

Fn/FnMut/FnOnce 作为 trait 限界时,影响使用对应函数变量的方式,如 FnOnce 类型在函数内只能调用一次,而 Fn/FnMut 可以调用多次。

// F 是 FnOnce, 所以可以也可以传入满足前面的 Fn/FnMut 闭包
fn foobar<F>(f: F)
    where F: FnOnce() -> String
{
    println!("{}", f());
    println!("{}", f()); // 错误:error: use of moved value: `f`
}

// 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 是 FnMutt 类型,且输入是 &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 类型函数参数,只能传入函数指针(函数名),而不能传入闭包函数 。但对于用 Fn/FnOnce/FnMut trait 限界的参数,可以传入 fn 函数或闭包函数。对于没有捕获环境中值的闭包,可以使用被隐式或显式(as 运算符)转换为 fn 指针:

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,具体取决于捕获的对象类型。

例如:如果所有捕获的对象都实现了 Send,则闭包也实现了 Send。

  • 只拥有共享引用的闭包,如果引用的对象都实现了 Clone 和 Copy,所以该闭包也实现了它们;
  • 对于有可变引用的闭包,没有实现 Clone 和 Copy;
  • 对于 move 闭包,如果 move 的所有值都能 Copy 则闭包能 Copy,都能 Clone 则闭包能 Clone;

Fn/FnMut/FnOnce 都是 trait,当作为函数输入参数值或返回值时(而非泛型参数限界),需要使用 trait object 值,如 &dyn Trait 或 =Box<dyn Trait> 或者使用 impl Trait

  • impl trait 在编译时静态确定的唯一类型,后续不能再变化;
  • &dyn trait 和 Box 是运行时动态分发的类型;
// 错误: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>;
}

闭包作为函数参数或返回值时,需要 'static lifetime,这是由于如果闭包包含超出作用域的借用时保存闭包是不安全的,加 'static 则表示传入的闭包在程序整个生命周期有效,进而里面的借用也一直有效。

对于 move 闭包,由于拥有上下文对象的所有权,该闭包具有 'static lifetime

// C 必须加 'static, 否则后续调用 Box::new() 报错。
impl BasicRouter {
    fn add_route<C>(&mut self, url: &str, callback: C)
        where C: Fn(&Request) -> Response + 'static
      {
          self.routers.insert(url.to_string(), Box::new(callback));
      }
}

闭包 lifetime:闭包返回引用时可能会有 lifetime 问题:

  1. 不能为闭包的借用参数和返回值定义 lifetime(但是可以使用外围函数定义的 ’lifetime)。
  2. 闭包返回值的 lieftime 要比输入参数长,但是由于不能指定 lifetime,不能表达这个语义;

解决办法:

  1. 使用 nightly toolchain 和开启 #![feature(closure_lifetime_binder)],可以为闭包函数指定 for <‘a> 语法的 lifetime:
  2. 或者,定义一个 helper 函数,可以指定闭包输入、输出参数所需的 lifetime,内部调用闭包;
  3. 或者,将闭包转换为 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 值, 则建议使用 Use variable rebinding in a separate scope for that:

// 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);
let closure = {
    // 建议使用下面的 rebingding 方式
    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) 指的是不返回的函数, 使用 ! 来表示: 用在表示永不返回的逻辑,例如无限循环、调用 panic! 宏或者退出程序:

  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() {
    // 未指定返回值,等效为返回 ();
    ()
}
rust-lang - 这篇文章属于一个选集。
§ 9: 本文

相关文章

1. 标识符和注释:identify/comment
·
Rust
Rust 标识符介绍
10. 泛型和特性:generic/trait
·
Rust
Rust 泛型和特性
11. 类型协变:type coercion
·
Rust
Rust 高级话题:子类型和类型协变
12. 迭代器:iterator
·
Rust
Rust 迭代器