rust学习二十.13、RUST的函数指针fn和匿名函数

函数指针是一个好东西。

一、简述

函数指针就是执行一段函数代码的指针。

根据官方的说法,函数指针实现了FnOnce,FnMut,Fn特质。

由于函数指针是一种数据类型,所以rustc允许它作为函数/方法的参数,这样就给程序设计添加了不少的灵活性.

我估摸着,rust设计者就是为了让rust能够适应潮流:在函数/方法中直接传递匿名函数/闭包

一个典型的带有函数指针的rust函数定义如下:

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32
这种定义方式其实某种程度上更加符合我的思维方式。
 
在java中,我们不能那么定义,最多只能定义一个函数接口为参数,例如:
 
void Fight(Tool tool,Target tar)

它看起来不如rust来的那么明显。rust的定义能够让我立刻意识到这是函数指针参数(意味着这是一段代码)。

当然,只要愿意,应该也可以把函数指针以不那么明显的方式定义,后文有例子.

二、示例

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

type  FnPointer = fn(i32,i32) -> String;

fn  link(f: FnPointer, x:i32,y:i32) {
   let dst= f(x,y);
   println!("{}",dst);
}

/**
 * 第一个参数是函数指针,fn指针实现了FnOnce,FnMut,Fn特质,所以可以接受匿名函数作为参数。
 */
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}


fn 书本例子() {
    let list_of_numbers = vec![1, 2, 3];
    //利用rustc,象我这样的傻瓜也能写rust!
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(|i| "0".to_owned()+&i.to_string()).collect();
    println!("{:?}", list_of_strings);
    let list_of_strings2: Vec<String> =
        list_of_numbers.iter().map(ToString::to_string).collect();
    println!("{:?}", list_of_strings2);
}


fn main() {
    //封装再封装,就可以解决一些复杂问题,至于是否高效是另外一回事了.
    let funcs=[add_one, multiply_10];
    let numbers = [123, 233, 3232, 32334, 5333];
    for i in 0..numbers.len() {
        let answer1 = do_twice(funcs[0], numbers[i]);
        let answer2=  do_twice(funcs[1], numbers[i]);
        println!("({}+1)*2= {},({}*10)*2={}",numbers[i],answer1,numbers[i],answer2);    
    }

    // do_twice同时也能接受匿名函数,rust的解释:fn指针实现了FnOnce,FnMut,Fn特质,所以可以接受匿名函数作为参数。
    
    println!("\n匿名函数也能被接受作为参数的演示....");
    let answer3 = do_twice(|x| x * 2+99, numbers[0]);
    println!("{}经过处理后等于{}",numbers[0],answer3);

    // 书本例子,让我们感兴趣是类似java等语言的流式操作
    书本例子();

    //类型别名
    let link_to = |x:i32,y:i32| x.to_string()+&y.to_string();
    link(link_to, 123,899);
}

 

上例基本模仿书本,主要演示两个问题:

1.函数指针如何定义

2.使用函数指针作为参数的函数,也能接收匿名函数为入参

另外顺便演示了如何让函数指针看起来不那么明显(效果上类似java等一些语言),以下代码用于达成这个目的:

type  FnPointer = fn(i32,i32) -> String;
fn  link(f: FnPointer, x:i32,y:i32) {
   let dst= f(x,y);
   println!("{}",dst);
}

 

如果函数指针比较复杂,也许用上别名也不错。

输出结果:

三、函数指针的替代方案

在函数/方法中除了可以通过指定函数指针的方式来传递函数,其实还有几种方式:

1.利用特质绑定+通用类型

2.还是特质绑定+通用类型,只不过是另外一种书写形式

3.使用特质对象+动态分发dyn

以下就是例子:

/**
 * 函数指针的替代者1  -- 利用特质绑定
 */ 
fn batman<T: FnOnce(i32,i32)->String>(f:T) {
    let result=f(1094,101);
    println!("{}",result);
}
/**
 * 函数指针的替代者2 -- 特质绑定的简化写法,rust 1.13引入的trait bound syntax
 */
fn eggman(f:impl FnOnce(i32,i32)->String) {
    let result=f(1094,103);
    println!("{}",result);
}
/**
 * 函数指针的替代者3 -- 使用特质对象
 */
fn pigman(f:&dyn Fn(i32,i32)->String) {
    let result=f(2094,103);
    println!("{}",result);
}

fn main(){
    batman(|x:i32,y:i32| x.to_string()+&y.to_string()+" 黑蝙蝠侠 🦇");
    eggman(|x:i32,y:i32| x.to_string()+&y.to_string()+" 蛋蛋大侠 🥚🥚");
    pigman(&|x:i32,y:i32| x.to_string()+&y.to_string()+" 猪猪侠士 🐷");   
}

 

输出结果:

这里例子中,比较特殊的就是pigman的定义:

fn pigman(f:&dyn Fn(i32,i32)->String)

这里的定义比较特殊,用上了&dyn,为什么要这样了?

  • 因为特质对象必须使用动态分发,这是rust规定的
  • 要使用&,是因为特质对象大小不确定,rustc不知道大小,所以只能加一个引用,以便构成一个指针(编译器可以知道大小)

最后,pigman用了Fn,而不是FnOnce,这是因为FnOnce要求移出,要求移出则必须实现Copy。为了不Copy,所以改为Fn。

记住,rust中函数/方法本身也是一种类型。

四、小结

1.函数指针fn的存在某种程度上方便了程序设计-这让程序设计更加灵活

2.函数指针实现了FnOnce,FnMut,Fn,按照rust的说法就是可以定义一个匿名函数作为函数指针传递给函数/方法

3.函数指针的存在,使得编写代码某种程度上方便了部分程序员

4.事实上,就是没有函数指针,其实也有其它方式能过实现类似的效果

 

posted @ 2025-04-10 19:09  正在战斗中  阅读(130)  评论(0)    收藏  举报