rust中的函数
函数
函数的声明使用关键字fn
。函数的参数需要使用类型注释,就像变量一样。
如果函数有返回值,必须在->
后面指出返回值的类型.
在函数中的最后一个表达式语句,将被当做函数的返回值使用。
我们也可以使用return
语句在最后一个表达式语句之前让函数返回一个值,即使是在loop
循环或者if
条件语句中都是可以的。
示例代码:
// rust中对于函数的定义的顺序没有限制
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!("{}", {});
}
}
//如果函数返回的是(),那么返回类型可以在函数签名中省略
fn fizzbuzz_to(n: u32) {
for n in 1..n+1 {
fizzbuzz(n);
}
}
方法
为某个类型的对象定义的函数就是方法。
这些方法可以访问对象的数据,并且可以通过self
访问该对象的其他方法。
方法定义在impl
块之下。
struct Point {
x: f64,
y: f64,
}
// 方法的实现代码块,Point的所有方法都实现在这里
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 {
// 这是一个实例方法
// &self是self:&Self的语法糖,其中Self是调用对象的类型
// 在这个例子中,Self=Rectangle
fn area(&self) -> f64 {
// 通过self和点运算符我们可以访问struct的属性
let Point{x: x1, y: y1} = self.p1;
let Point{x: x2, y: y2} = self.p2;
// abs是f64类型的方法,返回调用对象的绝对值
((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;
((x1 - x2).abs() + (y1 - y2).abs()) * 2.0
}
// 这个方法要求调用方法的对象是可修改的
// &mut self是self: &mut Self的语法糖
fn translate(&mut self, x: f64, y: f64) {
self.p1.x += x;
self.p2.x += x;
self.p1.y += y;
self.p2.y += y;
}
}
// Pair所拥有的资源:两个在堆内存上分配的整数
struct Pair(Box<i32>, Box<i32>);
impl Pair{
// 这个方法会消费调用对象的资源
// self是self:Self的语法糖
fn destroy(self){
let Pair(first, second) = self;
println!("Destory Pair({}, {})", first, second);
// 在离开作用域时,first和second会被释放掉
}
}
fn main(){
let rectangle = Rectangle{
p1: Point::origin(),
p2: Point::new(3.0, 4.0),
};
// 实例方法使用点操作符来调用
// 注意,第一个参数是隐式传递的
// 即: rectangle.perimeter() == Rectangle::perimeter(&rectangle)
println!("Rectangle perimeter: {}", rectangle.perimeter());
println!("Rectangle area: {}", rectangle.area());
let mut square = Rectangle{
p1: Point::origin(),
p2: Point::new(1.0, 1.0),
};
// 下述方法调用是错误的
// 因为rectangle是不可变的,但是translate方法要求的是一个可变对象
// rectangle.translate(1.0, 0.0);
// 可变对象调用可变方法
square.translate(1.0, 1.0);
let pair = Pair(Box::new(1), Box::new(1));
pair.destory();
// 下述调用是错误的
// 之前的destory调用,已经消费了pair对象
// 两个堆内存上的整数资源,已经被释放了
// pair.destory();
}
闭包
rust中的闭包,也被称为lambda表达式或者lambda函数,该函数可以捕获闭包环境中的变量。
例如,一个捕获变量x的闭包
|val| val + x
闭包的语法和功能使得它很适合即时使用的场景。调用一个闭包和调用一个函数是一样的。
其中,输入参数和返回参数的类型都可以通过类型推断得出,但是,必须指出输入参数的名称。
其他的闭包特征如下:
- 使用
||
而不是()
包裹输入变量 - 对于单个的表达式,可以选择省略函数体(
{}
),如果不是单个表达式,那么就不可省略 - 捕获外部作用域中的环境变量的能力
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));
// 一个没有参数的闭包,返回值的类型是i32
// 返回值类型是通过类型推断得到的
let one = || 1;
println!("closure returning one: {}", one());
}
捕获
闭包具有固有的灵活性,闭包会按照功能的需要去做能保证闭包在没有类型注解的情况下正常的工作。
这样变量的捕获就会很灵活的去适应相应的使用场景,有时候是转移,有时候是借用。
闭包可以通过如下的方式去捕获变量
- 通过引用的方式:
&T
- 通过可变引用的方式:
&mut T
- 通过值的方式:
T
更多的时候都是通过引用来进行捕获,只有在需要的时候才会使用后面两种方式。
示例代码:
fn main(){
use std::mem;
let color = String::from("green");
// 一个打印color变量的闭包
// 这个闭包会借用color变量,把借用变量和闭包存储在变量print中
// 这个借用会一直存在直到print的最后一次调用
// println!只要求参数是不可变的引用,因此它不会施加任何其他的限制
let print = || println!("`color`: {}", color);
// 调用使用借用的闭包
print();
// color可以被再次地不可变的借用,因为闭包只是持有一个对变量color的不可变的引用
let _reborrow = &color;
print();
// 在对print的最后一次调用之后,一个对color变量的转移或者借用是允许的
let _color_moved = color;
let mut count = 0;
// 一个实现对变量count自增的闭包,能够接受的参数是&mut count或者count
// 但是&mut count的限制更少,因此接受了这个参数。
// 立即借用了count。
// 在闭包inc上需要mut修饰,是因为在闭包内部存储了一个&mut变量。
// 因而,在调用闭包的时候会改变闭包,所以需要一个mut关键字
let mut inc = || {
count += 1;
println!("`count`: {}", count);
};
// 使用一个可变借用调用闭包
inc();
// 该闭包仍然以可变的方式借用着变量count,因为后续还有对该闭包的调用.
// 尝试再一次的借用count变量将会导致编译错误
// let _reborrow = &count;
inc();
// inc闭包不再需要借用&mut count了。
// 因而,再次借用count不会导致错误.
let _count_reborrowed = &mut count;
// 一个不可复制的类型
let movable = Box::new(3);
// mem::drop需要T类型,因此必须以值的方式来捕获变量。
// 一个可复制类型将会把外部的变量复制到闭包的内部,从而屏蔽外部的变量
// 这样在闭包内部,外部的变量是无法访问到的
// 一个不可复制的类型必须转移,因此movable立即转移到闭包内部
let consume = || {
println!("`movable`: {:?}", movable);
mem::drop(movable);
};
// consume消费变量因此,它只能被调用一次
consume();
}
使用move
关键字在垂直管道符号的前面,强制把闭包捕获的变量的所有权转移给闭包
fn main(){
// vec具有不可复制语义
let haystack = vec![1, 2, 3];
let contains = move | needle | haystack.contains(needle);
println!("{}", contains(&1));
println!("{}", contains(&4));
// 下面的调用是错误的,因为借用检查不允许再次使用已经被转移的变量
// println!("There're {} elements in vec", haystack.len());
// 如果想使上面的调用能够通过编译器的检查
// 我们需要把contains闭包签名中的move关键字去掉
// 这样闭包会把对变量haystack的变量借用变成不变的借用
// 这样一来,haystack变量仍然是可用的
}
作为输入参数
当rust在即时使用闭包的时候,选择如何去捕获变量,在大多数情况都没有类型的注解,在定义函数的时候,这种歧义性是不允许的。
当把一个闭包当作一个输入参数的时候,闭包的完整类型必须使用几个traits
中的一个来进行注释。
这些traits
如下所示,按照限制逐渐递减的方式排列如下:
Fn
:通过引用(&T
)来捕获变量FnMut
:通过可变引用(&mut T
)来捕获变量FnOnce
:通过值(T
)来捕获变量
在一个变量接一个变量处理的处理的接触上,编译器将可能会用限制最少的来捕获变量。
例如,我们假设有个参数的类型注解是FnOnce
。这个类型说明,闭包将可能使用T
,&T
或者&mut T
的方式来捕获变量,
但是最终,编译器选择根据变量在闭包中的使用方式来决定变量的捕获方式。
这是因为,如果转移是可能的,那么任何形式的借用都是可能的。
但是,需要注意的是反过来是不可行的。如果参数的类型注解为Fn
,那么以&mut T
或者T
方式捕获变量是不允许的。
示例代码如下:
// 一个接受闭包变量作为参数,并且调用闭包的函数。
// <F>表明F是一个泛型参数
fn applay<F>(f: F) where F: FnOnce{
f();
}
// 一个接受闭包变量作为参数,并且返回i32类型的值
fn applay_to_3<F>(f: F) -> i32 where F: Fn(i32) -> i32 {
f(3)
}
fn main(){
use std::mem;
let greeting = "hello";
// 一个不可复制类型
// to_owned从借用变量一个拥有所有权的变量
let mut farewell = "goodbye".to_owned();
// 捕获两个变量
// greeting通过引用的方式
// farewell通过值的方式
let diary = || {
// greeting通过引用方式,需要指定trait的类型是Fn
println!("I said {}.", greeting);
// 可变性使得fareware以可变引用的方式捕获,需要指定的trait类型是FnMut.
farewell.push_str("!!!");
println!("Then I screamed {}", farewell);
println!("Now I can slepp. zzzzzz");
// 手动释放,使得farewell以值的方式被捕获,现在需要指定的trait类型是FnOnce.
mem::drop(farewell);
};
// 调用applay函数使用闭包
applay(diary);
// double满足apply_to_3函数中对于trait类型的指定
let double = |x| 2 * x;
println!("3 doubled: {}", apply_to_3(double));
}
类型匿名
闭包可以简洁地从闭包作用域中捕获变量。我们注意到当把闭包当作函数的一个参数使用时,需要函数的参数是泛型类型的参数。
之所以要这样做,是因为,函数的定义。
fn apply<F>(f: F) where F: FnOnce() {
f();
}
当一个闭包被定义的时候,编译器会隐式地创建一个匿名结构体去存储捕获的变量,同时通过几个traits
:Fn
、FnMut
、FnOnce
中的一个来为这个未知的类型实现相应的功能。这个类型会被赋值给存储的变量,一直到闭包被调用的时候。
由于这个新类型是未知的类型,在函数中的任何使用都需要泛型。然而,一个未被绑定的类型参数<T>
仍然是有歧义的,而且不会被编译器所允许。因而,绑定traits
:Fn
、FnMut
和FnOnce
中的一个来指定类型是必要的。
fn apply<F>(f: F) where F: Fn() where {
f();
}
fn main(){
let x = 7;
// 捕获变量x到匿名类型中,并且为其实现Fn.
// 然后赋值给变量print
let print = || println!("{}", x);
apply(print);
}
输入函数
闭包可以被当作参数,实际上函数也是可以的。
如果你声明一个接受一个闭包作为参数的函数,那么任何满足这个闭包的trait绑定的函数,都可以当作参数传递给这个函数
// 声明了一个函数
// 接受一个泛型类型为F的参数,该泛型类型的绑定是Fn,
// 然后调用该参数所代表的函数
fn call_me<F: Fn()>(f: F){
f();
}
// 定一个函数,满足Fn的绑定
fn function(){
println!("I am a function!");
}
fn main(){
// 定以一个闭包满足Fn绑定
let closure = || println!("I am a closure!");
call_me(closure);
call_me(function);
}
traits Fn
、FnMut
和FnOnce
揭示了一个闭包如何捕获闭包作用域中的变量。
作为输出参数
闭包可以作为函数的输入参数,同样的,因此返回闭包也可以作为函数的输出参数。
然而,匿名的闭包类型在定义时是未知的,因为我们只能通过使用impl Trait
来返回他们。
能够返回闭包的有效的traits:
Fn
FnMut
FnOnce
除此之外,必须使用move
关键字,这标志着所有的变量捕获都是通过值的方式进行的。
之所以这样要求是因为,任何通过引用方式捕获的变量在函数一退出的时候,就会释放该引用,
这样在闭包中会留下无效的引用。
fn create_fn() -> impl Fn(){
let text = "Fn".to_owned();
move || println!("This is a: {}", text)
}
fn creat_fn_mut() -> impl FnMut() {
let text = "FnMut".to_owned();
move || println!("This is a: {}", text)
}
fn create_fn_once() -> impl FnOnce() {
let text = "FnOnce".to_owned();
move || println!("This is a: {}", text)
}
fn main(){
let fn_plain = create_fn();
let mut fn_mut = create_fn_mut();
let fn_once = create_fn_once();
fn_plain();
fn_mut();
fn_once();
}
rust标准库中的示例
Iterator::any
Iterator::any
是一个函数,该函数的参数是一个迭代器,如果有任何元素满足条件该函数就会返回true
,否则返回false
。
该函数的声明如下:
pub trait Iterator {
// 将要被迭代的元素的类型
type Item;
// any函数的参数是&mut self类型,意味着该函数可能会借用或者修改调用函数的对象
// 但是不会消费该对象
fn any<F>(&mut self, f: F) -> bool where F: FnMut(Self::Item) -> bool {}
}
fn main(){
let vec1 = vec1[1, 2, 3];
let vec2 = vec2[4, 5, 6];
println!("2 in vec1: {}", vec1.iter().any(|&x| x == 2));
println!("4 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
是一个函数,该函数会遍历迭代器,找到第一个满足条件的元素。
如果没有满足条件的元素,那么该函数会返回None
。
该函数的签名如下:
pub trait Iterator {
// 将要被遍历的元素的类型
type Item;
// find函数的参数是&mut self
// 意味着调用该函数的对象将会被借用或者修改,但是不会被消费掉
fn find<P>(&mut self, predicate: P) -> Option<Self::Item> where P: FnMut(&Self::Item) -> bool {}
}
fn main(){
let vec1 = vec1[1, 2, 3];
let vec2 = vec2[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));
}
Iterator::find
返回的是对找到的目标元素的引用。但是,如果我们需要的是目标元素的下标,那么我们可以使用Iterator::position
。
fn main(){
let vec = vec1[1, 9, 3, 3, 13, 2];
let index_of_first_even_number = vec.iter().position(|x| x % 2 == 0);
assert_eq!(index_of_first_even_number, Some(5));
let index_of_first_negative_number = vec.iter().position(|x| x < &0);
assert_eq!(index_of_first_negative_number, None);
}
高阶函数
Rust提供了对高阶函数的支持。这些函数接受一个或者多个函数作为参数,或者返回一个更有用的函数作为返回值。
高阶函数和惰性迭代器为rust提供了函数式编程的功能。
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_squared| n_squared < upper).filter(|&n_square| is_ood(n_squared)).fold(0, |acc, n_squared| acc + n_squared);
println!("functional style: {}", sum_of_squared_odd_numbers);
}
发散函数
发散函数从来都不会返回。他们用!
标识,这代表一个空类型。
fn foo() -> ! {
panic!("This call never returns.");
}
和rust中的很多其他类型不同的是,这个类型是不能被实例化的,因为这个类型没有可能的取值集合。
需要注意的是,它和单元类型()
是不同的,单元类型有一个值()
。
例如,这个函数像正常的一样返回,虽然在它的返回值中没有任何信息
fn some_fn(){
()
}
fn main(){
let a: () = some_fn();
println!("This function returns and you can see this line");
}
与上面的函数相反的是,下面这个函数不会把控制流返回给调用者
#![feature(never_type)]
fn main(){
let x: ! = panic!("This call never returns.");
println!("You will never see this line");
}
虽然这看起来像一个抽象的概念,但是实际上它非常有用并且通常很方便。
这个类型的主要优势是,它可以转换为任何类型,因而它可以用在任何需要准确类型的地方,例如match
的匹配分支。
因而,我们可以像下面这样写代码:
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));
}
该类型也是无限循环函数的返回值类型,例如网络服务端或者终结进程的函数(exit
)。