rust高级特征-高级函数

时间:2024-11-14 07:27:37

函数指针fn(x)->y

函数的类型是 fn (使用小写的 “f” )以免与 Fn 闭包 trait 相混淆。fn 被称为 函数指针(function pointer)。

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

编译

 cargo run
    Blocking waiting for file lock on build directory
   Compiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.69s
     Running `target/debug/blog`
The answer is: 12

三种闭包类型:Fn,FnMut,FnOnce

Fn:以不可变形式捕获外部变量

FnMut:以可变的形式捕获外部变量

FnOnce:以转移所有权的方式捕获外部变量

闭包作为参数的好处:既可以传入参数,也可以传入函数指针

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice<T>(f: T, arg: i32) -> i32
where
    T: Fn(i32) -> i32,
{
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

#![allow(unused)]
fn main() {
    let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> = list_of_numbers.iter().map(|i| i.to_string()).collect();
    //使用函数指针替代
    // 看ToString::to_string)源码可知,任何实现Display特征的类型,都会实现ToString特征(也就可以调用to_string()方法)
    let list_of_strings: Vec<String> = list_of_numbers.iter().map(ToString::to_string).collect();
}

元组结构体和元组结构体成员

#![allow(unused)]
fn main() {
    enum Status {
        // 这些项使用 () 作为初始化语法,这看起来就像函数调用,
        //同时它们确实被实现为返回由参数构造的实例的函数。它们也被称为实现了闭包 trait 的函数指针,
        Value(u32),
        Stop,
    }

    // Status::Value当作一个函数指针,入参就是u32类型,返回值是Status类型
    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
    // 使用闭包初始化 `Status::Value`
    let list_of_statuses: Vec<Status> = (0u32..20)
        .map(|x| Status::Value(x)) // 闭包
        .collect();
}

返回一个闭包

可以的

#![allow(unused)]
fn main() {}

fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

错误代码

  • 由于闭包捕获 x,并且 x 可能会在不同的分支中被捕获为不同的值,Rust 必须确保闭包的生命周期与 x 的生命周期一致,而这需要更精确的类型推断。
#![allow(unused)]
fn main() {}

fn returns_closure(x: i32) -> impl Fn(i32) -> i32 {
    if x > 0 {
        move |y| x + y
    } else {
        move |y| x - y
    }
}

解决办法:使用特征对象进行动态派遣

#![allow(unused)]
fn main() {}

fn returns_closure(x: i32) -> Box<dyn Fn(i32) -> i32> {
    if x > 0 {
        Box::new(move |y| x + y)
    } else {
        Box::new(move |y| x - y)
    }
}

动态派遣 (dyn Fn(i32) -> i32):

  • 使用 dyn Fn(i32) -> i32 是 Rust 中的 动态特征对象(动态派遣)(trait object),它允许在运行时确定闭包的类型,而不需要在编译时确定所有具体的类型。
  • 在你的原始代码中,impl Fn(i32) -> i32 是一个 静态派遣 类型,它要求编译器在编译时推断出闭包的具体类型。由于你在 if 和 else 分支中返回不同的闭包,这会使得 Rust 无法统一推断返回的闭包类型,因此需要用动态特征对象来解决。

在原始的代码中,当使用 impl Fn(i32) -> i32 时,Rust 需要在编译时确定返回的闭包类型。但由于你在 if 和 else 分支中创建了两种不同的闭包,它们的类型会有所不同,导致 Rust 无法推导出统一的返回类型。

通过使用 Box<dyn Fn(i32) -> i32>,你将返回值的类型改成了 动态特征对象,这意味着 Rust 在编译时不再需要知道闭包的具体类型,而是通过运行时的动态派遣来决定如何调用闭包。此外,闭包被存储在堆上(通过 Box),因此它们可以拥有不同的大小,而 Rust 不需要在栈上存储这些不同大小的闭包。