[易学易懂系列|rustlang语言|零基础|快速入门|(7)函数Functions与闭包Closure]

有意思的基础知识

函数Functions与闭包Closure


我们今天再来看看函数。

在Rust,函数由关键词:fn来定义。

如果有参数,必须定义参数的数据类型。

一般情况下,函数返回元组( tuple )类型,如果要返回特定的类型,一般要用符号:

-> 来定义。

请看代码如下:

1.main函数:

fn main() {
   println!("Hello, world!");
}

2.传递参数:

fn print_sum(a: i8, b: i8) {
   println!("sum is: {}", a + b);
}

3.有返回值:

/ 01. Without the return keyword. Only last expression returns.
fn plus_one(a: i32) -> i32 {
   a + 1
   // There is no ending ; in the above line. It means this is an expression which equals to `return a+1;`
}

// 02. With the return keyword.
fn plus_two(a: i32) -> i32 {
   return a + 2; // Returns a+2. But, this's a bad practice.
   // Should use only on conditional returns, except in the last expression
}

4.函数指针,作为数据类型:

// 01. Without type declarations
let b = plus_one;
let c = b(5); //6

// 02. With type declarations
let b: fn(i32) -> i32 = plus_one;
let c = b(5); //6

闭包:

1.闭包,即匿名函数或lambda函数。

2.参数类型与返回值,是可选的。

闭包,一句话来说,就是特殊的函数。

首先我们来看看正常函数:

fn main() {
 let x = 2;
 println!("{}", get_square_value(x));
}

fn get_square_value(x: i32) -> i32 {
   x * x
}

然后,我们用闭包来改写:

fn main() {
   let x = 2;
   let square = |x: i32| -> i32 { // Input parameters are passed inside | | and expression body is wrapped within { }
       x * x
  };
   println!("{}", square(x));
}

进一步简写:

fn main() {
   let x = 2;
   let square = |x| x * x; // { } are optional for single-lined closures
   println!("{}", square(x));
}

我们来简单对比一下函数与闭包,请看下面程序 :

fn main() {
   // 函数形式:累加1.
   fn function(i: i32) -> i32 {
       i + 1
  }

   //闭包形式:完整定义
   let closure_annotated = |i: i32| -> i32 { i + 1 };
   //闭包形式:简化定义,利用rust的类型推导功能自动进行类型推导,这个更常用
   let closure_inferred = |i| i + 1;
   let i = 1;

   // 调用函数与闭包
   println!("function: {}", function(i));
   println!("closure_annotated: {}", closure_annotated(i));
   println!("closure_inferred: {}", closure_inferred(i));

   //简单的闭包,只返回一个integer值,返回值 类型将如系统自动推导,即系统对1这个数字,自动判断,并推导出它是integer
   let one = || 1;
   println!("closure returning one: {}", one());
}

我们看到,函数定义更复杂。

我们再来看看下面的程序,比对一下:

fn main() {
   
   let x = 4;//定义一个integer变量
   // 函数形式:累加.
   fn function(i: i32) -> i32 {
       i + x//!!!!!这里编译报错!!!,函数不能从运行环境上获取其他变量!!!!
  }

   //闭包形式:完整定义
   let closure_annotated = |i: i32| -> i32 { i + x };//用闭包可以从环境中获取x变量
   //闭包形式:简化定义,这个更常用
   let closure_inferred = |i| i + x;
   let i = 1;

   // 调用函数与闭包
   println!("function: {}", function(i));
   println!("closure_annotated: {}", closure_annotated(i));
   println!("closure_inferred: {}", closure_inferred(i));

   //简单的闭包,只返回一个integer值,返回值 类型将如系统自动推导,即系统对1这个数字,自动判断,并推导出它是integer
   let one = || 1;
   println!("closure returning one: {}", one());
}

编译器报错!

编译器详细错误信息为:

error[E0434]: can't capture dynamic environment in a fn item
--> src\main.rs:5:13
|
5 |         i + x
|             ^
|
= help: use the `|| { ... }` closure form instead

这里的编译器详细指出:函数不能动态从环境(当前运行环境或上下文)获得x,并提示用闭包。

我们再来看看如下代码:

fn main() {
   use std::mem;
   let color = "green";

   // A closure to print `color` which immediately borrows (`&`) `color` and
   // stores the borrow and closure in the `print` variable. It will remain
   // borrowed until `print` is used the last time.
   //
   // `println!` only requires arguments by immutable reference so it doesn't
   // impose anything more restrictive.
   //定义一个简单的打印闭包,直接共享借用color变量,并把闭包绑定到print变量
   let print = || println!("`color`: {}", color);

   // Call the closure using the borrow.
   //直接调用这个闭包(借用color)
   print();

   // `color` can be borrowed immutably again, because the closure only holds
   // an immutable reference to `color`.
   //color变量可以再次共享借用_reborrow,因为闭包print只是用了共享借用color
   let _reborrow = &color;
   print();

   // A move or reborrow is allowed after the final use of `print`
   //上面调用 了print()闭包后,这个color可以移动move(即转移其数据所有权)
   let _color_moved = color;

   let mut count = 0;
   // A closure to increment `count` could take either `&mut count` or `count`
   // but `&mut count` is less restrictive so it takes that. Immediately
   // borrows `count`.
   //
   // A `mut` is required on `inc` because a `&mut` is stored inside. Thus,
   // calling the closure mutates the closure which requires a `mut`.
   //这里用可变借用变量count, 所以闭包也定义为可变的引用
   let mut inc = || {
       count += 1;
       println!("`count`: {}", count);
  };

   // Call the closure using a mutable borrow.
   //通过可变借用调用闭包
   inc();

   // The closure still mutably borrows `count` because it is called later.
   // An attempt to reborrow will lead to an error.
   // let _reborrow = &count;//这里如果共享借用将报错,因为count已经可变借用给闭包,再借用将通不过编译器
   // ^ TODO: try uncommenting this line.
   inc();

   // The closure no longer needs to borrow `&mut count`. Therefore, it is
   // possible to reborrow without an error
   //这个语句下面,没有再调用inc()闭包的代码,即现在闭包没有再可变借用变更count,现在就可以用可变借用来借用count
   let _count_reborrowed = &mut count;

   // A non-copy type.
   //定义一个引用变更或智能指针变量(非复制类型)
   let movable = Box::new(3);

   // `mem::drop` requires `T` so this must take by value. A copy type
   // would copy into the closure leaving the original untouched.
   // A non-copy must move and so `movable` immediately moves into
   // the closure.
   //定义一个闭包,把变量movable移动到闭包里,用方法mem::drop直接把变量内存释放
   let consume = || {
       println!("`movable`: {:?}", movable);
       mem::drop(movable);
  };

   // `consume` consumes the variable so this can only be called once.
   //调用闭包方consume直接把变量释放掉,所以这个闭包只能调用一次
   consume();
   // consume();//第二次调用会报错!
   // ^ TODO: Try uncommenting this lines.
}

上面的代码用来以下两个标准库

Boxstd::mem::drop

以上代码打印结果为:

`color`: green
`color`: green
`count`: 1
`count`: 2
`movable`:

我们再来看看更复杂的例子,把闭包当作一个输入参数:

// A function which takes a closure as an argument and calls it.
// <F> denotes that F is a "Generic type parameter"
//定义一个函数apply,其参数为:F类型的闭包
fn apply<F>(f: F)
where
  // The closure takes no input and returns nothing.
  //这里指定F类型的闭包为FnOnce类型,即它只能调用一次
  //这个闭包没有参数与没有返回值
  F: FnOnce(),
{
  // ^ TODO: Try changing this to `Fn` or `FnMut`.
  //可以尝试改变这个F类型为 Fn(不可变函数)或FnMut(可变函数)
  f();
}

// A function which takes a closure and returns an `i32`.
//定义一个函数apply_to_3,其参数为闭包,返回值为i32
fn apply_to_3<F>(f: F) -> i32
where
  // The closure takes an `i32` and returns an `i32`.
  //定义这个闭包类型为Fn(不可变函数),返回一个i32值
  F: Fn(i32) -> i32,
{
  f(3)
}

fn main() {
  use std::mem;

  let greeting = "hello";
  // A non-copy type.
  // `to_owned` creates owned data from borrowed one
  //非复制类型
  //to_owned()方法从一个借用变量中得到数据所有权
  let mut farewell = "goodbye".to_owned();

  // Capture 2 variables: `greeting` by reference and
  // `farewell` by value.
  //闭包获取两个变量:
  //通过引用获取greeting
  //通过值 获取farewell
  let diary = || {
      // `greeting` is by reference: requires `Fn`.
      //greeting从引用获取值
      println!("I said {}.", greeting);

      // Mutation forces `farewell` to be captured by
      // mutable reference. Now requires `FnMut`.
      //farewell从可变引用得到数据值,所以是可修改的
      farewell.push_str("!!!");
      println!("Then I screamed {}.", farewell);
      println!("Now I can sleep. zzzzz");

      // Manually calling drop forces `farewell` to
      // be captured by value. Now requires `FnOnce`.
      //手动地释放farewell的资源
      mem::drop(farewell);
  };

  // Call the function which applies the closure.
  //把闭包diary当作一个参数传给函数apply
  apply(diary);

  // `double` satisfies `apply_to_3`'s trait bound
  //定义一个闭包,乘以2
  let double = |x| 2 * x;

  println!("3 doubled: {}", apply_to_3(double));
}

以上结果为:

I said hello.
Then I screamed goodbye!!!.
Now I can sleep. zzzzz
3 doubled: 6

我们看到有一个where关键词,我们这里简单介绍下where的用法 。

之前,我们再来讨论一下特征变量的绑定,如下:

// Define a function `printer` that takes a generic type `T` which
// must implement trait `Display`.
//定义一个printer的函数,这个函数的参数T,必须实现特征Display
fn printer<T: Display>(t: T) {
   println!("{}", t);
}

上面就是简单的特征变量的绑定,那T也可以绑定多个特征:

use std::fmt::{Debug, Display};

fn compare_prints<T: Debug + Display>(t: &T) {
   println!("Debug: `{:?}`", t);
   println!("Display: `{}`", t);
}

fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
   println!("t: `{:?}`", t);
   println!("u: `{:?}`", u);
}

fn main() {
   let string = "words";
   let array = [1, 2, 3];
   let vec = vec![1, 2, 3];

   compare_prints(&string);
   //compare_prints(&array);
   // TODO ^ Try uncommenting this.

   compare_types(&array, &vec);
}

那这个多个特征绑定定义,就可以用where语句来表示,更加清晰,如下两种定义是一样的:

impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}

// Expressing bounds with a `where` clause
//用where子语句来定义多个特征绑定定义
impl <A, D> MyTrait<A, D> for YourType where
  A: TraitB + TraitC,
  D: TraitE + TraitF {}

我们来看看例子:

use std::fmt::Debug;

trait PrintInOption {
   fn print_in_option(self);
}

// Because we would otherwise have to express this as `T: Debug` or
// use another method of indirect approach, this requires a `where` clause:
impl<T> PrintInOption for T where
   Option<T>: Debug {
   // We want `Option<T>: Debug` as our bound because that is what's
   // being printed. Doing otherwise would be using the wrong bound.
   fn print_in_option(self) {
       println!("{:?}", Some(self));
  }
}

fn main() {
   let vec = vec![1, 2, 3];

   vec.print_in_option();
}

上面代码结果为:

Some([1, 2, 3])

以上就是Rust的函数与闭包的基本知识。

以上,希望对你有用。

如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust

[易学易懂系列|rustlang语言|零基础|快速入门|(7)|函数Functions与闭包Closure]的更多相关文章

  1. [易学易懂系列|rustlang语言|零基础|快速入门|(28)|实战5:实现BTC价格转换工具]

    [易学易懂系列|rustlang语言|零基础|快速入门|(28)|实战5:实现BTC价格转换工具] 项目实战 实战5:实现BTC价格转换工具 今天我们来开发一个简单的BTC实时价格转换工具. 我们首先 ...

  2. [易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链]

    [易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链] 项目实战 实战4:从零实现BTC区块链 我们今天来开发我们的BTC区块链系统. 简单来说,从数据结构的 ...

  3. [易学易懂系列|rustlang语言|零基础|快速入门|(26)|实战3:Http服务器(多线程版本)]

    [易学易懂系列|rustlang语言|零基础|快速入门|(26)|实战3:Http服务器(多线程版本)] 项目实战 实战3:Http服务器 我们今天来进一步开发我们的Http服务器,用多线程实现. 我 ...

  4. [易学易懂系列|rustlang语言|零基础|快速入门|(25)|实战2:命令行工具minigrep(2)]

    [易学易懂系列|rustlang语言|零基础|快速入门|(25)|实战2:命令行工具minigrep(2)] 项目实战 实战2:命令行工具minigrep 我们继续开发我们的minigrep. 我们现 ...

  5. [易学易懂系列|rustlang语言|零基础|快速入门|(24)|实战2:命令行工具minigrep(1)]

    [易学易懂系列|rustlang语言|零基础|快速入门|(24)|实战2:命令行工具minigrep(1)] 项目实战 实战2:命令行工具minigrep 有了昨天的基础,我们今天来开始另一个稍微有点 ...

  6. [易学易懂系列|rustlang语言|零基础|快速入门|(23)|实战1:猜数字游戏]

    [易学易懂系列|rustlang语言|零基础|快速入门|(23)|实战1:猜数字游戏] 项目实战 实战1:猜数字游戏 我们今天来来开始简单的项目实战. 第一个简单项目是猜数字游戏. 简单来说,系统给了 ...

  7. [易学易懂系列|rustlang语言|零基础|快速入门|(5)|生命周期Lifetime]

    [易学易懂系列|rustlang语言|零基础|快速入门|(5)] Lifetimes 我们继续谈谈生命周期(lifttime),我们还是拿代码来说话: fn main() { let mut a = ...

  8. [易学易懂系列|rustlang语言|零基础|快速入门|(22)|宏Macro]

    [易学易懂系列|rustlang语言|零基础|快速入门|(22)|宏Macro] 实用知识 宏Macro 我们今天来讲讲Rust中强大的宏Macro. Rust的宏macro是实现元编程的强大工具. ...

  9. [易学易懂系列|rustlang语言|零基础|快速入门|(21)|智能指针]

    [易学易懂系列|rustlang语言|零基础|快速入门|(21)|智能指针] 实用知识 智能指针 我们今天来讲讲Rust中的智能指针. 什么是指针? 在Rust,指针(普通指针),就是保存内存地址的值 ...

  10. [易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理]

    [易学易懂系列|rustlang语言|零基础|快速入门|(20)|错误处理] 实用知识 错误处理 我们今天来讲讲Rust中的错误处理. 很多语言都有自己的错误处理方式,比如,java是异常处理机制. ...

随机推荐

  1. KNN原理小结

    K近邻法(K-nearest neighbors,KNN)既可以分类,也可以回归. KNN做回归和分类的区别在于最后预测时的决策方式.KNN做分类时,一般用多数表决法:KNN做回归时,一般用平均法. ...

  2. Nodejs 之非阻塞 I/O、异步、事件驱动

    1.非阻塞 I/O var fs = require('fs'); console.log('); fs.readFile('mime.json',function (err,data) {//rea ...

  3. jquery 教程网

  4. JavaScript基础修炼(14)

    目录 一. PCM格式是什么 二. 浏览器中的音频采集处理 三. 需求实现 方案1——服务端FFmpeg实现编码 方案2——ScriptProcessorNode手动处理数据流 参考文献 示例代码托管 ...

  5. CSS未完

    CSS介绍 CSS(Cascading Style Sheet,层叠样式表)定义如何显示HTML元素. 当浏览器读到一个样式表,它就会按照这个样式表来对文档进行格式化(渲染). CSS语法 CSS实例 ...

  6. Linux下面误删除文件使用extundelete工具恢复介绍

    操作系统版本:CentOS release 6.4 (Final)      软件版本:extundelete-0.2.4.tar.bz2 PS:该软件恢复文件系统仅支持ext2/ext3/ext4 ...

  7. sql中循环的存储过程

    ), a2 bigint, a3 bigint) returns void as $$declare ii integer; begin II:; FOR ii IN a2..a3 LOOP INSE ...

  8. BZOJ 1303 中位数图 题解

    题面 因为所求的是中位数,所以考虑改变原序列.把大于 b 的数全部变为 1,小于 b 的数变为 −1,等于 b 则为 0.问题就变为求存在几个包含 b的区间和为 0 . 根据乘法原理,我们枚举每一个l ...

  9. 父进程pid和子进程pid的大小关系

    如果进程ID最大值没有达到系统进程数的上限,子进程比父进程ID大.但是如果进程ID达到上限,系统会分配之前分配但是已经退出的进程ID给新进程,这样有可能出现子进程ID比父进程小.

  10. fatal: refusing to merge unrelated histories问题解决

    git中pull或merge时偶尔出现