函数(Rust入门)
注:此文章仅对相关知识点简略讲解,rust 学习路线陡峭,如果发现看不懂,记不住,不理解,活用搜索引擎,这些知识点可以说不是你一个人的误区,所以有很多人都贡献了不错的学习资料
本文章参考自官方学习文档及社区学习文档以下是参考链接:
官方:
社区:
直接上手学习推荐:
概述点
函数(function
)使用 fn
关键字来声明。函数的参数需要标注类型,就和变量一样,另外如果函数返回一个值,返回类型必须在箭头 ->
之后指定
本文内容:
函数定义
函数(function
)使用 fn
关键字来声明。函数的参数需要标注类型,就和变量一样,另外如果函数返回一个值,返回类型必须在箭头 ->
之后指定。
函数最后的表达式将作为返回值。也可在函数内使用 return
语句来提前返回值。return
甚至也可在循环或 if
内部使用。
让我们使用函数来重写 FizzBuzz 程序吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| fn main() { fizzbuzz_to(100); }
fn is_divisible_by(lhs: u32, rhs: u32) -> bool { if rhs == 0 { return false; }
lhs % rhs == 0 }
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 fizzbuzz_to(n: u32) { for n in 1..n + 1 { fizzbuzz(n); } }
|
方法
方法(method
)是依附于对象的函数。这些方法通过关键字 self
来访问对象中的数据和其他。方法在 impl
代码块中定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| struct Point { x: f64, y: f64, }
impl Point { fn origin() -> Point { Point { x: 0.0, y: 0.0 } }
fn new(x: f64, y: f64) -> Point { Point { x: x, y: y } } }
struct Rectangle { p1: Point, p2: Point, }
impl Rectangle { fn area(&self) -> f64 { let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2;
((x1 - x2) * (y1 - y2)).abs() }
fn perimeter(&self) -> f64 { let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2;
2.0 * ((x1 - x2).abs() + (y1 - y2).abs()) }
fn translate(&mut self, x: f64, y: f64) { self.p1.x += x; self.p2.x += x;
self.p1.y += y; self.p2.y += y; } }
struct Pair(Box<i32>, Box<i32>);
impl Pair { fn destroy(self) { let Pair(first, second) = self;
println!("Destroying Pair({}, {})", first, second);
} }
fn main() { let rectangle = Rectangle { p1: Point::origin(), p2: Point::new(3.0, 4.0), };
println!("Rectangle perimeter: {}", rectangle.perimeter()); println!("Rectangle area: {}", rectangle.area());
let mut square = Rectangle { p1: Point::origin(), p2: Point::new(1.0, 1.0), };
square.translate(1.0, 1.0);
let pair = Pair(Box::new(1), Box::new(2));
pair.destroy();
}
|
闭包
Rust
中的闭包(closure
),也叫做 lambda
表达式或者 lambda
,是一类能够捕获周围作用域中变量的函数。例如,一个可以捕获 x
变量的闭包如下:
它们的语法和能力使它们在临时(on the fly
)使用时相当方便。调用一个闭包和调用一个函数完全相同,不过调用闭包时,输入和返回类型两者都可以自动推导,而输入变量名必须指明。
其他的特点包括:
- 声明时使用
||
替代 ()
将输入参数括起来。
- 函数体定界符(
{}
)对于单个表达式是可选的,其他情况必须加上。
- 有能力捕获外部环境的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| fn main() { fn function (i: i32) -> i32 { i + 1 }
let closure_annotated = |i: i32| -> i32 { i + 1 }; 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));
let one = || 1; println!("closure returning one: {}", one()); }
|
捕获
闭包天生就是灵活的,它会自动满足函数功能的要求,使得闭包不需要类型说明就可以工作。这允许变量捕获(capture)灵活地适应使用场合,既可移动(move)又可借用(borrow)变量。闭包可以通过以下手段捕获变量:
- 通过引用:
&T
- 通过可变引用:
&mut T
- 通过值:
T
闭包更倾向于通过引用来捕获变量,并且只在被要求时才使用其他手段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| fn main() { use std::mem;
let color = "green";
let print = || println!("`color`: {}", color);
print(); print();
let mut count = 0;
let mut inc = || { count += 1; println!("`count`: {}", count); };
inc(); inc();
let movable = Box::new(3);
let consume = || { println!("`movable`: {:?}", movable); mem::drop(movable); };
consume(); }
|
在竖线 |
之前使用 move
会强制闭包取得被捕获变量的所有权:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fn main() { let haystack = vec![1, 2, 3];
let contains = move |needle| haystack.contains(needle);
println!("{}", contains(&1)); println!("{}", contains(&4));
}
|
作为输入参数
虽然 Rust
无需类型说明就能在大多数时候完成变量捕获,但在编写函数时,这种模糊写法是不允许的。当以闭包作为输入参数时,必须指出闭包的完整类型,它是通过使用以下 trait
中的一种来指定的。其受限制程度按以下顺序递减:
Fn
:表示捕获方式为通过引用(&T
)的闭包
FnMut
:表示捕获方式为通过可变引用(&mut T
)的闭包
FnOnce
:表示捕获方式为通过值(T
)的闭包
译注:顺序之所以是这样,是因为 &T
只是获取了不可变的引用,&mut T
则可以改变 变量,T
则是拿到了变量的所有权而非借用。
对闭包所要捕获的每个变量,编译器都将以限制最少的方式来捕获。
译注:这句可能说得不对,事实上是在满足使用需求的前提下尽量以限制最多的方式捕获。
例如用一个类型说明为 FnOnce
的闭包作为参数。这说明闭包可能采取 &T
,&mut T
或 T
中的一种捕获方式,但编译器最终是根据所捕获变量在闭包里的使用情况决定捕获方式。
这是因为如果能以移动的方式捕获变量,则闭包也有能力使用其他方式借用变量。注意反过来就不再成立:如果参数的类型说明是 Fn
,那么不允许该闭包通过 &mut T
或 T
捕获变量。
在下面的例子中,试着分别用一用 Fn
、FnMut
和 FnOnce
,看看会发生什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| fn apply<F>(f: F) where F: FnOnce() {
f(); }
fn apply_to_3<F>(f: F) -> i32 where F: Fn(i32) -> i32 {
f(3) }
fn main() { use std::mem;
let greeting = "hello"; let mut farewell = "goodbye".to_owned();
let diary = || { println!("I said {}.", greeting);
farewell.push_str("!!!"); println!("Then I screamed {}.", farewell); println!("Now I can sleep. zzzzz");
mem::drop(farewell); };
apply(diary);
let double = |x| 2 * x;
println!("3 doubled: {}", apply_to_3(double)); }
|
类型匿名
闭包从周围的作用域中捕获变量是简单明了的。这样会有某些后果吗?确实有。观察一下使用闭包作为函数参数,这要求闭包是泛型的,闭包定义的方式决定了这是必要的。
1 2 3 4 5
| fn apply<F>(f: F) where F: FnOnce() { f(); }
|
当闭包被定义,编译器会隐式地创建一个匿名类型的结构体,用以储存闭包捕获的变量,同时为这个未知类型的结构体实现函数功能,通过 Fn
、FnMut
或 FnOnce
三种 trait
中的一种。
若使用闭包作为函数参数,由于这个结构体的类型未知,任何的用法都要求是泛型的。然而,使用未限定类型的参数 <T>
过于不明确,并且是不允许的。事实上,指明为该结构体实现的是 Fn
、FnMut
、或 FnOnce
中的哪种 trait
,对于约束该结构体的类型而言就已经足够了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
fn apply<F>(f: F) where F: Fn() { f(); }
fn main() { let x = 7;
let print = || println!("{}", x);
apply(print); }
|
输入函数
既然闭包可以作为参数,你很可能想知道函数是否也可以呢。确实可以!如果你声明一个接受闭包作为参数的函数,那么任何满足该闭包的 trait
约束的函数都可以作为其参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| fn call_me<F: Fn()>(f: F) { f() }
fn function() { println!("I'm a function!"); }
fn main() { let closure = || println!("I'm a closure!");
call_me(closure); call_me(function); }
|
多说一句,Fn
、FnMut
和 FnOnce
这些 trait
明确了闭包如何从周围的作用域中捕获变量。
作为输出函数
闭包作为输入参数是可能的,所以返回闭包作为输出参数(output parameter
)也应该是可能的。然而返回闭包类型会有问题,因为目前 Rust
只支持返回具体(非泛型)的类型。按照定义,匿名的闭包的类型是未知的,所以只有使用 impl Trait
才能返回一个闭包。
返回值的合法 trait
和前面的略有不同:
Fn
:和前面的一样
FnMut
:和前面的一样
FnOnce
:不平常的事情正是发生在这里。总之现在你需要返回 FnBox
类型,目前该类型还是不稳定的。这个情况估计将来会改进。
除此之外,还必须使用 move
关键字,它表明所有的捕获都是通过值进行的。这是必须的,因为在函数退出时,任何通过引用的捕获都被丢弃,在闭包中留下无效的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| fn create_fn() -> impl Fn() { let text = "Fn".to_owned();
move || println!("This is a: {}", text) }
fn create_fnmut() -> impl FnMut() { let text = "FnMut".to_owned();
move || println!("This is a: {}", text) }
fn main() { let fn_plain = create_fn(); let mut fn_mut = create_fnmut();
fn_plain(); fn_mut(); }
|
std中的例子
本小节列出几个标准库中使用闭包的例子。
Iterator::any
Iterator::any
是一个函数,若传给它一个迭代器(iterator
),当其中任一元素满足谓词(predicate
)时它将返回 true
,否则返回 false
(译注:谓词是闭包规定的,true
/false
是闭包作用在元素上的返回值)。它的签名如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| pub trait Iterator { type Item;
fn any<F>(&mut self, f: F) -> bool where F: FnMut(Self::Item) -> bool {}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6];
println!("2 in vec1: {}", vec1.iter() .any(|&x| x == 2)); println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2));
let array1 = [1, 2, 3]; let array2 = [4, 5, 6];
println!("2 in array1: {}", array1.iter() .any(|&x| x == 2)); println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2)); }
|
Iterator::find
Iterator::find
是一个函数,在传给它一个迭代器时,将用 Option
类型返回第一个满足谓词的元素。它的签名如下
1 2 3 4 5 6 7 8 9 10 11
| pub trait Iterator { type Item;
fn find<P>(&mut self, predicate: P) -> Option<Self::Item> where P: FnMut(&Self::Item) -> bool {} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6];
let mut iter = vec1.iter(); let mut into_iter = vec2.into_iter();
println!("Find 2 in vec1: {:?}", iter .find(|&&x| x == 2)); println!("Find 2 in vec2: {:?}", into_iter.find(| &x| x == 2));
let array1 = [1, 2, 3]; let array2 = [4, 5, 6];
println!("Find 2 in array1: {:?}", array1.iter() .find(|&&x| x == 2)); println!("Find 2 in array2: {:?}", array2.into_iter().find(|&&x| x == 2)); }
|
高阶函数
Rust 提供了高阶函数(Higher Order Function, HOF),指那些输入一个或多个函数,并且/或者产生一个更有用的函数的函数。HOF 和惰性迭代器(lazy iterator)给 Rust 带来了函数式(functional)编程的风格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| fn is_odd(n: u32) -> bool { n % 2 == 1 }
fn main() { println!("Find the sum of all the squared odd numbers under 1000"); let upper = 1000;
let mut acc = 0; for n in 0.. { let n_squared = n * n;
if n_squared >= upper { break; } else if is_odd(n_squared) { acc += n_squared; } } println!("imperative style: {}", acc);
let sum_of_squared_odd_numbers: u32 = (0..).map(|n| n * n) .take_while(|&n| n < upper) .filter(|&n| is_odd(n)) .fold(0, |sum, i| sum + i); println!("functional style: {}", sum_of_squared_odd_numbers); }
|
Option
和迭代器都实现了不少高阶函数。
发散函数
发散函数(diverging function)绝不会返回。 它们使用 !
标记,这是一个空类型。
1 2 3
| fn foo() -> ! { panic!("This call never returns."); }
|
和所有其他类型相反,这个类型无法实例化,因为此类型可能具有的所有可能值的集合为空。 注意,它与 () 类型不同,后者只有一个可能的值。
如下面例子,虽然返回值中没有信息,但此函数会照常返回。
1 2 3 4 5 6 7 8
| fn some_fn() { () }
fn main() { let a: () = some_fn(); println!("This function returns and you can see this line.") }
|
下面这个函数相反,这个函数永远不会将控制内容返回给调用者。
1 2 3 4 5 6
| #![feature(never_type)]
fn main() { let x: ! = panic!("This call never returns."); println!("You will never see this line!"); }
|
虽然这看起来像是一个抽象的概念,但实际上这非常有用且方便。这种类型的主要优点是它可以被转换为任何其他类型,从而可以在需要精确类型的地方使用,例如在 match
匹配分支。 这允许我们编写如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| fn main() { fn sum_odd_numbers(up_to: u32) -> u32 { let mut acc = 0; for i in 0..up_to { let addition: u32 = match i%2 == 1 { true => i, false => continue, }; acc += addition; } acc } println!("Sum of odd numbers up to 9 (excluding): {}", sum_odd_numbers(9)); }
|
这也是永远循环(如 loop
{})的函数(如网络服务器)或终止进程的函数(如 exit()
)的返回类型。