[易學易懂系列|rustlang語言|零基礎|快速入門|(7)|函數Functions與閉包Closure]


[易學易懂系列|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




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM