函数 常规函数 特点
函数都拥有显示的类型签名;
函数可分为三种类型:自由函数 、关联函数 和方法 ;
函数自身也是一种类型(函数项类型 )
自由函数
最普通的函数,他的参数、返回值等都一目了然。
1 2 3 4 5 6 7 8 fn sum (a: i32 , b: i32 ) -> i32 { a + b } fn main (){ assert_eq! (3 , sum (1 , 2 )); }
关联函数 / 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct A (i32 , i32 );impl A { fn sum (a: i32 , b: i32 ) -> i32 { a + b } fn math (&self ) -> i32 { Self ::sum (self .0 , self .1 ) } } fn main (){ let a = A (1 , 2 ); assert_eq! (3 , A::sum (1 , 2 )); assert_eq! (3 , a.math ()); }
函数项类型
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 struct A (i32 , i32 );impl A { fn sum (a: i32 , b: i32 ) -> i32 { a + b } fn math (&self ) -> i32 { Self ::sum (self .0 , self .1 ) } } pub fn main () { let a = A (1 , 2 ); let add = A::sum; let add_math = A::math; assert_eq! (add (1 , 2 ), A::sum (1 , 2 )); assert_eq! (add_math (&a),a.math ()); println! ("{:?}" , std::mem::size_of_val (&add)); println! ("{:?}" , std::mem::size_of_val (&add_math)); println! ("{:?}" , std::mem::size_of_val (&A)); println! ("{:?}" , std::mem::size_of_val (&a)); }
函数指针类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type RGB = (i16 , i16 , i16 );fn color (c: &str ) -> RGB { (1 , 1 , 1 ) } fn shwo (c: fn (&str ) -> RGB) { println! ("{:?}" , c ("black" )); println! ("c: {:?}" , std::mem::size_of_val (&c)); } fn main (){ let rgb : fn (&str ) -> RGB; rgb = color; shwo (rgb); println! ("rgb: {:?}" , std::mem::size_of_val (&rgb)); }
结论:
函数项类型可以通过显式指定函数类型转换为一个函数指针类型;
在写代码的时候,尽可能地去使用 函数项类型 ,不到万不得已不要使用函数指针类型,这样有助于享受零大小类型的优化;
闭包 函数无法捕获环境变量,这时就需要使用闭包。
1 2 3 4 5 6 7 8 9 10 11 fn counter (i:i32 ) -> fn (i32 ) -> i32 { fn inc (n: i32 )-> i32 { n+i } inc } fn main () { let f = counter (2 ); assert_eq! (3 ,f (1 )); }
闭包与函数的异同 闭包可以捕获环境中的自由变量
1 2 3 4 5 6 7 8 fn counter (i: i32 ) -> impl FnMut (i32 ) -> i32 { move | n | n + i } pub fn main () { let mut f = counter (2 ); assert_eq! (3 , f (1 )) }
闭包可以与函数指针互通
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type RGB = (i16 , i16 , i16 );fn color (c: &str ) -> RGB { (1 , 1 , 1 ) } fn shwo (c: fn (&str ) -> RGB) { println! ("{:?}" , c ("black" )); } fn main () { let rgb = color; shwo (rgb); let c = | s: &str | { (1 , 2 , 3 ) }; shwo (c); }
Rust闭包的实现原理 rust中的闭包并没有引入特殊的语法,而是一种编译器的语法糖。
闭包的使用场景
未捕获环境变量(所有权)
捕获但修改环境变量(可变引用)
捕获但未修改环境变量(不可变引用)
未捕获环境变量
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 fn main () { let c1 = || println! ("hello" ); c1 (); } #![feature(unboxed_closures, fn_traits)] struct Closure <T> { env_var: T, } impl <T> FnOnce <()> for Closure <T> { type Output = (); extern "rust-call" fn call_once (self , args: ()) -> () { println! ("hello" ); } } fn main () { let c = Closure { env_var: ()}; c.call_once (()); }
当你创建闭包的时候,编译器会解析你的闭包,然后生成一个匿名结构体Closure
,其中的字段env_var
用于存储捕获的自由变量;在上面的场景中闭包未捕获任何变量,所以main
函数中创建Closure
的字段值用()
来表示;
接下来在为这个匿名的结构体Closure
实现FnOnce<()>
这个trait;其中Arges
这个参数是闭包自身的参数,即闭包调用时传入的参数;
当闭包被调用的时候等价于调用匿名结构体实现的call_once
这个方法;该方法会消耗掉该结构体的实例;
编译器将FnOnce
类型的闭包看成是函数指针
1 2 3 4 5 6 7 8 9 fn main () { let c1 = || { "c1" ; }; let c2 = || { "c2" ; }; let v = [c1, c2]; let i = "c3" ; let c3 = || { i }; let v = [c1, c2, c3] }
捕获可修改环境变量
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 fn main () { let mut arr = [1 , 2 , 3 ]; let mut c2 = | i | { arr[0 ] = i; println! ("{:?}" , arr); } c2 (0 ); } #![feature(unboxed_closures, fn_traits)] struct Closure { env_var: [i32 , 3 ], } impl FnOnce <(i32 , )> for Closure { type Output = (); extern "rust-call" fn call_once (mut self , args: (i32 , )) -> () { self .env_var[0 ] = args.0 ; println! ("{:?}" , self .env_var); } } impl FnMut <(i32 , )> for Closure { extern "rust-call" fn call_mut (&mut self , args: (i32 , )) -> () { self .env_var[0 ] = args.0 ; println! ("{:?}" , self .env_var); } } fn main () { let arr = [1 , 2 , 3 ]; let mut c = Closuse { env_var: arr }; c.call_mut ((0 , )); }
此处根据rust-call
的约定 args
的参数类型必须是元组类型,这是Rust API
的约定;
当闭包被调用的时候等价于调用匿名结构体实现的call_mut
这个方法;该方法会修改该结构体的实例;
捕获但未修改环境变量
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 fn main () { let anwser = 42 ; let c3 = || { println! ( "The answer to the Ultimate Question of Life, The Universe, and Everything is {}" , anwser ) }; c3 (); } #![feature(unboxed_closures, fn_traits)] struct Closure { env_var: i32 , } impl FnOnce <( )> for Closure { type Output = (); extern "rust-call" fn call_once (mut self , args: ()) -> () { println! ( "The answer to the Ultimate Question of Life, The Universe, and Everything is {}" , self .env_var ) } } impl FnMut <()> for Closure { extern "rust-call" fn call_mut (&mut self , args: ()) -> () { println! ( "The answer to the Ultimate Question of Life, The Universe, and Everything is {}" , self .env_var ) } } impl fn <()> for Closure { extern "rust-call" fn call (&self , args: ()) -> () { println! ( "The answer to the Ultimate Question of Life, The Universe, and Everything is {}" , self .env_var ) } } fn main () { let anwser = 42 ; let mut c = Closure {env_var: anwser}; c.call (); }
可以看出要实现Fn
必须先实现FnMut
,要实现FnMut
必须先实现FnOnce
;
实现总结 这里指的是编译器生成的默认结构体
如果没有捕获任何变量,则实现FnOnce
如果有捕获变量,且对捕获变量进行了修改,则实现FnMut
如果有捕获变量,但未对变量进行修改,则实现Fn
两条特殊情况
编译器会将FnOnce
当成fn(T)
函数指针去看待。
Fn
/FnMut
/FnOnce
这三者trait
的关系是依次继承,他们正好对应所有权语义三件套 。
Fn()
是FnMut()
的子特型,而FnMut()
是FnOnce()
的子特型,
逃逸闭包与非逃逸闭包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #![feature(unboxed_closures, fn_traits)] fn c_mut () -> impl FnMut (i32 ) -> [i32 ; 3 ] { let mut arr = [0 , 1 , 2 ]; move |i| {arr[0 ] = i; arr} } fn main () { let i = 42 ; let mut arr_clouse = c_mut (); println! ("{:?}" , arr_clouse (i)); }
返回不在函数调用过程中被销毁的闭包称为逃逸闭包 ,否则就是非逃逸闭包
1 2 3 4 5 6 7 8 9 10 #![feature(unboxed_closures, fn_traits)] fn c_mut2 () -> impl for <'a >FnMut (&'a str ) -> String { let mut s = "hello" .to_string (); move |i| {s += i; s} } fn main () { let i = "world" let mut = arr_clouse = c_mut2 (); }
此处,s
是一个动态大小的类型,当move
移动时,移动的是指向推上的指针;根据Rust
内存管理机制,s
销毁后,他所指向的堆上的数据也会被销毁掉,这是move
中的s
就变成了悬垂指针 ,这是rust
所不允许的。
唯一不可变引用 捕获方式中有一种被称为唯一不可变借用 的特殊类型的借用捕获,这种借用不能在语言的其他任何地方使用,也不能显式地写出。唯一不可变借用发生在修改可变引用的引用对象(referent)时 ,如下面的示例所示:
1 2 3 4 5 6 7 8 9 10 11 fn main () { let mut a = [1 , 2 , 3 ]; let x = &mut a; { let mut c = || {(*x)[0 ] = 0 ;}; let y = &x; c (); } let z = &x; }
在这种情况下,不能去可变借用 x
,因为 x
没有标注 mut
。但与此同时,如果不可变借用 x
,那对其赋值又会非法,因为 & &mut
引用可能不是唯一的,因此此引用不能安全地用于修改值。所以这里闭包使用了唯一不可变借用:它采用了不可变的方式借用了 x
,但是又像可变借用一样,当然前提是此借用必须是唯一的。在上面的例子中,解开 y
那行上的注释将产生错误,因为这将违反闭包对 x
的借用的唯一性;z
的声明是有效的,因为闭包的生存期在块结束时已过期,从而已经释放了对 x
的借用。
闭包自身所实现的trait
Sized(默认实现)
Copy / Clone(取决于环境变量是否实现)
如果环境变量实现了 Copy / Clone,闭包如果以可变借用方式 捕获环境变量,并对其进行修改,则闭包自身不会实现 Copy / Clone;(防止出现多个闭包的可变借用)
如果环境自身是 Move 语义,则闭包内捕获环境变量的操作涉及修改环境变量或者消耗环境变量 ,则闭包自身不会实现 Copy / Clone;(防止出现多个闭包的可变借用)
Sync / Send
如果所有捕获变量均实现了 Sync,则闭包实现 Sync;
如果环境变量都不是[唯一不可变引用]方式捕获的,并且都实现了 Sync,则闭包实现 Send;
如果环境变量是以[唯一不可变引用]、[可变引用]、Copy 或 Move 所有权捕获的,那闭包实现 Send;