Rust 入门之函数和注释实例详解
目录
- 写在前面
- 函数
- 参数
- 语句和表达式
- 返回值
- 注释
写在前面
今天我们来学习 Rust 中的函数,最后会捎带介绍一下如何在 Rust 中写注释。也是比较轻量级的一节,大家快速过一下即可。
函数
函数本身是各个语言都支持的类型,我们此前已经多次使用 fn main()
这个函数来承载业务逻辑,fn
可以用来声明一个函数,而 main
函数跟其他语言一样,可以理解为程序启动的【起点】,一切逻辑从这里开始。
Rust 本身的命名规范是【snake case】,即下划线 + 小写,这个其实各个语言都有自己规范,分清楚环境即可。
fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("Another function."); }
这里的 another_function 就是个没有入参,没有出参的函数,命名遵循 snake case,很好理解。
Rust 中的函数跟其他语言是一样的,用 fn
来声明,后面加上函数名,小括号里面可以放入参,之后可以定义出参,最后用花括号来承载函数体。调用函数也不复杂,函数名后面跟上小括号+参数即可,注意 scope 就行,这里是因为我们的 another_function
就在当前包下,所以直接就那来调用。调用的时候要保证【函数所在的 scope 是对 caller 可见】的即可。
我们在 rust-learn 项目下通过 cargo new functions
新建一个项目,试一下上面的代码:
$ cargo run =============================== Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions) Finished dev [unoptimized + debuginfo] target(s) in 1.76s Running `target/debug/functions` Hello, world! Another function.
另外需要强调一点,Rust 文件内函数定义并不要求顺序,只要定义在 scope 内就能解析,比如 main 函数先于 another_function 定义是没问题的。
参数
还是基于我们此前的 another_function,我们尝试加一下入参,看看应该怎么做:
fn main() { another_function(5); } fn another_function(x: i32) { println!("The value of x is: {x}"); }
执行过后,结果如下:
$ cargo run =============================== Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions) Finished dev [unoptimized + debuginfo] target(s) in 0.62s Running `target/debug/functions` The value of x is: 5
此时 another_function 增加了一个参数 x,我们声明其类型为 i32。在 main 函数中调用的时候,传入我们的参数 5,最后被打印出来。
可能有的地方会特意提一下这两个概念:
- 形参:是在定义函数时使用的参数,目的是用来接收调用该函数时传进来的实际参数,即 parameter;
- 实参:是在调用时传递给函数的参数,即 arguments。
但通常说起来的时候我们不太区分,对我们来说统一叫【参数】即可。
上面示例中,我们定义入参是这样的:fn another_function(x: i32)
。
这里【冒号 + 空格 + 类型】的写法我们已经见过很多次了,那能不能不带类型呢?我直接写个 fn another_function(x)
,具体格式留给编译器来推断 ok 不?
在 Rust 中这件事是不 ok的,按照规范,对于每个入参你都必须清晰地指明【类型】,这样编译器也省事,报错时也能更精准给出相关判断。如果我们需要多个入参,用【逗号】分隔即可:
fn main() { print_labeled_measurement(5, 'h'); } fn print_labeled_measurement(value: i32, unit_label: char) { println!("The measurement is: {value}{unit_label}"); }
执行结果如下:
$ cargo run ======================== Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions) Finished dev [unoptimized + debuginfo] target(s) in 0.87s Running `target/debug/functions` The measurement is: 5h
语句和表达式
Rust 本身是一个基于表达式的语言,所以这两个概念我们先区分一下,语句(statements),表达式(expressions)是什么区别?
Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resulting value.
简单说,就是看有没有【返回值】,无返回值的是语句,有返回值的是表达式,表达式可以是一个语句的组成部分。
举个例子:let y = 6;
这就是一个【语句】,而 6
就是一个【表达式】,在 Rust 中你是不能做 let x = (let y = 6);
这样的操作的,因为括号里面的部分是个语句,语句没有返回值,那么该拿什么给 x 赋值呢?
所以,不像其他语言,可能允许类似 x = y = 6
,这样让 x 和 y 都赋值了 6。Rust 是不允许这样的。
fn main() { let y = { let x = 3; x + 1 }; println!("The value of y is: {y}"); }
比如上面这个案例,在花括号这个 scope 中,我们定义了 x 变量,将其赋值为 3,然后将 x+1
这个表达式返回,所以 y 被赋值为 4。
花括号里面的部分就是一个表达式,返回了 4 。注意 x + 1
的结尾没有分号,这也是表达式的特征。这里千万不能加分号,要想清楚。如果你想用一个表达式返回,就不加分号。加了之后变成了语句,但也不会返回什么东西。
Rust 函数体则是由一系列【语句】+ 默认可选的一个【表达式】组成。为什么是可选的?因为类似我们前面的函数,没有返回值,不需要最后的这个【表达式】。
返回值
Rust 是不支持命名返回值的(这一点跟 Golang 有所不同),函数定义出参的部分需要用【箭头符号】显式地声明。
不像很多函数要求显式的 return 返回值,Rust 默认会返回最后的表达式的值。当然我们如果想 early return 也是 ok的,但大多数函数不会写 return 这个关键字,而是隐式地返回最后一个表达式。我们来看一个例子:
fn five() -> i32 { 5 } fn main() { let x = five(); println!("The value of x is: {x}"); }
这里的 five 函数非常简单,只有一个 5 作为表达式返回,不需要 return。
这是完全合法的 Rust 函数,出参只有一个 i32。我们加上入参,再看一个例子:
fn main() { let x = plus_one(5); println!("The value of x is: {x}"); } fn plus_one(x: i32) -> i32 { x + 1 }
此时我们有一个 i32 入参,也有一个 i32 出参,函数体是一个简单的表达式 x + 1。运行上面代码打印的结果是 The value of x is: 6
,符合预期。
我们试试给 x + 1
后面加上个分号看看:
fn main() { let x = plus_one(5); println!("The value of x is: {x}"); } fn plus_one(x: i32) -> i32 { x + 1; }
此时运行结果果然报错(这个不是运行时报错,是编译阶段识别的)
$ cargo run ==================== Compiling functions v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/functions) error[E0308]: mismatched types --> src/main.rs:7:24 | 7 | fn plus_one(x: i32) -> i32 { | -------- ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression 8 | x + 1; | - help: remove this semicolon For more information about this error, try `rustc --explain E0308`. error: could not compile `functions` due to previous error
问题在于,plus_one 说了会有返回值 i32,但到最后也没发现【表达式】,此时 Rust 默认会返回 () 一个空的 tuple(我们上一节讲过,这个叫 unit),所以报错叫做【 mismatched types】,而不是类似【no return value】,这里是不是就理解了?
没有返回值的函数,本质上是返回了一个 unit:
// Functions that "don't" return a value, actually return the unit type `()` 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); } }
问题又来了,那 Rust 能不能支持多个出参呢?类似 Golang 中的:
func addsub(x, y int) (int, int) { return x + y, x - y }
其实 Rust 对这个事情的解决方案就是我们已经见过多次的 tuple:
fn addsub(x: isize, y: isize) -> (isize, isize) { (x + y, x - y) } fn my_func() -> (u8, bool) { (1, true) }
圆括号千万不能少,记住我们 return 的是个 tuple,不是多个单独的值。
这里有一个可运行的 online 示例,大家可以复习一下 tuple,结合多个返回值体会一下:
fn swap(x: i32, y: i32) -> (i32, i32) { return (y, x); } fn main() { // return a tuple of return values let result = swap(123, 321); println!("{} {}", result.0, result.1); // destructure the tuple into two variables names let (a, b) = swap(result.0, result.1); println!("{} {}", a, b); }
注释
注释其实比较简单,我们快速提一下。
Rust 的行注释就是常见的 //
双斜杠,如果一行放不下,需要多行的话,也需要在每一行前面加.
fn main() { // I'm feeling lucky today let lucky_number = 7; }
文档注释有些许的区别,这里需要用 ///
三斜杠,这样能够辅助生成 HTML 文档。
/// Adds one to the number given. /// /// # Examples /// /// ``` /// let arg = 5; /// let answer = my_crate::add_one(arg); /// /// assert_eq!(6, answer); /// ``` pub fn add_one(x: i32) -> i32 { x + 1 }
以上就是Rust 入门之函数和注释实例详解的详细内容,更多关于Rust 函数注释的资料请关注我们其它相关文章!