mian 函数代表程序的入口,对于二进制可执行文件来讲,main 函数必不可少,对于库函数来讲,main 函数没有必要。
函数定义
Rust 中函数通过 fn 定义。代码示例
pub fn fizz_buzz(num: i32) -> String {
if num % 15 == 0 {
return "fizzbuzz".to_string();
} else if num % 3 == 0 {
return "fizz".to_string();
} else if num % 5 == 0 {
return "buzz".to_string();
} else {
return num.to_string();
}
}
fn main () {
assert_eq!(fizz_buzz(15), "fizzbuzz".to_string());
assert_eq!(fizz_buzz(3), "fizzbuzz".to_string());
assert_eq!(fizz_buzz(5), "buzz".to_string());
assert_eq!(fizz_buzz(13), "13".to_string());
}
函数解析: fn 关键字定义了 fizz_buzz 函数,其函数签名pub fn fizz_buzz(num: i32) -> String 反应函数的类型约定: 传入 i32 类型,返回 String 类型。Rust 编译器会严格遵守此类型的契约,如果传入或返回类型不对,则编译时报错。
作用域与生命周期
Rust 语言的作用域是静态作用域,即词法作用域(Lexocal Scope),由一对花括号来开辟,其作用域在词法分析阶段就已经确定了,不会动态改变。示例如下
fn main () {
let v = "hello world!";
assert_eq!(v, "hello world!");
let v = "hello Rust!";
assert_eq!(v, "hello Rust!");
{
let v = "Hello World!"
assert_eq!(v, "Hello World!");
}
assert_eq!(v, "hello Rust!");
}
首先声明 v 变量,赋值为 hello world!, 此时断言验证其值,再次通过 let 声明变量绑定 v 赋值为 hello Rust! 这种连续定义同名变量的做法叫做变量遮蔽(Variable Shadow),最终变量 v 的值是由第二个变量定义所决定的。
函数指针
在 Rust 中,函数为一等公民,意味着函数自身就可以作为函数的参数和返回值使用。示例:
fn sum(a: i32, b: i32) -> i32 {
a + b
}
fn product(a: i32, b: i32) -> i32 {
a * b
}
fn main () {
let a = 2;
let b = 3;
assert_eq!(math(sum, a, b), 5);
assert_eq!(math(product, a, b), 6);
}
在 main 函数中,调用了 math 函数两次,分别传入了 sum 和 product 作为参数。而 sum 和 product 分别是用于求和求积的两个函数,他们的类型分别是fn (i32, i32) -> i32 ,所以可以作为参数传给 math 函数。这里直接使用函数的名字来作为函数指针。
fn is_true -> bool {true}
fn true_maker() -> fn() -> bool{is_true}
fn main() {
assert_eq!(true_maker()(), true);
}
定义函数中 is_true, 返回true ,还定义了函数 true_maker,返回 fn() -> bool 类型,器函数体内直接将 is_true 函数指针返回,此处函数名称作为函数指针,如果加上括号,代表调用此函数。
在 main 函数的断言中, true_maker()()调用相当于(true_maker())() 首先调用 true_maker() ,会返回 is_true 函数指针,然后再调用 is_true()函数,最终得到 true。
CTFE 机制
Rust 编译器也可以像 C++ 或者 D 语言一样,拥有编译时函数执行(Compile-time Function Execution, CTFE)能力。示例为 const fn
// #![feature(const_fn)]
const fn init_len() -> usize {
return 5;
}
fn main () {
let arr = [0, init_len()];
}
代码中,使用了 const fn 来定义函数 init_len ,该函数返回一个固定值 5 。并且在 main 函数中,通过 [0; N] 的方式初始化初始值为 0、长度为 N 的数组,其中 N 是调用函数 init_len 来求得的。
Rust 固定长度数组必须在编译期就知道长度,否则就会编译出错,所以 init_len必须在编译期求值。这就是 CTFE 的能力。
使用 const fn 定义的函数,必须是可确定值,不能存在歧义。与 fn 定义函数的区别在于,const fn 可以强制编译器在编译期执行函数。其中关键字 const 一般用于定义全局变量。
除了 const fn 之外,官方还在实现 const generics 特性。支持 const generics 特性,将可以实现类似 impl <T, const N: unsize> Foo for[T;N] {…} 的代码,可以为所有长度的数组实现 trait Foo。可使得使用数组的体验得到很大提升。
Rust 中的 CTFE 是有 miri 来执行的。miri 是一个 MIR 解释器,目前已经被集成到了 Rust 编译器 rustc 中。Rust 编译器目前可以支持的常量表达式有: 字面量、元祖、数组、字段结构体、枚举,只包含但行代码的块表达式、范围等。Rust 想要拥有完善的 CTFE 支持,还需要很多工作要做。
闭包
闭包也称匿名函数,闭包有一下几个特点: * 可以像函数一样被调用。 * 可以驳货上下文环境中的自由变量。 * 可以自动推断输入和返回的类型。
fn main () {
let out = 42;
// fn add (i: i32, j: i32) -> i32 {i + j + out}
fn add (i: i32, j: i32) -> i32 { i + j}
let closure_annotated = |i: i32, j: i32| -> i32 {i + j + out}
let closure_inferred = |i, j| i + j + out;
let i = 1;
let j = 2;
assert_eq!(3, add(i, j));
assert_eq!(45, closure_annotated(i, j));
assert_eq!(45, closure_inferred(i, j));
}
上述代码中,main 函数中定义了另外一个函数 add, 以及两个闭包 closure_annotated 和 closure_inferred。 闭包调用和函数调用非常想,但是闭包合函数有一个重要的区别,就是闭包可以捕获外部变量,而函数不能。这与 JavaScript 有很大的差别。JavaScript 函数本身就可以获取外部变量,只是获取到外部变量不一定就是闭包。 闭包也可以作为函数参数和返回值,但是用起来略有区别。
fn closure_math<F: Fn() -> i32>(op: F) -> i32 {
op()
}
fn main () {
let a = 2;
let b = 3;
assert_eq!(closure_math(|| a + b), 5);
assert_eq!(closure_math(|| a * b), 6);
}
上述代码中,定义了函数 closure_math,其参数是一个泛型 F, 并且该泛型 Fn() -> i32 trait 的限定,代表该函数只允许实现 Fn() -> i32 trait 的类型作为参数。
Rust 中的闭包实际上就是由一个匿名结构体和 trait 来组合实现的。所以,在 main 函数调用 math 函数时,分别传入 || a + b 和 || a * b 都可以实现 Fn() -> i32。 在 math 函数内部,直接调用传入闭包。
闭包作为返回值
fn tow_times_impl() -> impl Fn(i32) -> i32 {
let i = 2;
move |j| j * i
}
fn main () {
let result = tow_times_impl();
assert_eq!(result(2), 4);
}
在上述代码中,使用了 impl Fn(i32) -> i32 作为函数的返回值,他表示实现 Fn(i32) -> i32 的类型,在函数定义是并不知道具体的返回类型,但是在函数调用时,编译器会推断出来。这个过程是零成本抽象的,一切都发生在编译期。
需要注意的是,two_times_impl 中最后返回闭包时使用了 move 关键字,这是因为一般情况下,闭包默认会按引用捕获变量,如果将闭包返回,则引用也会跟着返回,但是在整个函数调用完毕后,函数内的本地变量 i 就会被销毁,那么随着闭包返回的变量 i 的引用,也就成了悬垂指针。
Rust 是注重内存安全的语言,如果不适用 move 关键字,编译器会报错。
使用 move 关键字,将捕获变量 i 的所有权转移到闭包中,就不会按引用进行捕获变量,闭包才可安全返回。
#Rust学习