Hàm
Hàm hiện diện ở mọi nơi trong Rust. Bạn đã thấy một trong những hàm quan
trọng nhất: hàm main
, điểm khởi đầu (entry point) của nhiều chương trình. Bạn
cũng đã thấy từ khóa fn
, từ khóa cho phép bạn khai báo hàm mới.
Rust code sử dụng snake case cho tên hàm và tên biến. Trong snake case tất cả các ký tự là chữ thường và các từ phân cách bởi dấu gạch dưới. Dưới đây là chương trình có chứa ví dụ về định nghĩa hàm:
Filename: src/main.rs
fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("Another function."); }
Định nghĩa hàm trong Rust bắt đầu với fn
và có một cặp dấu ngoặc đơn sau tên
hàm. Dấu ngoặc nhọn cho trình biên dịch biết điểm bắt đầu và kết thúc thân hàm.
Chúng ta có thể gọi bất kì hàm nào chúng ta đã định nghĩa bằng cách nhập tên của
nó theo sau là đóng mở ngoặc đơn. Bởi vì another_function
được định nghĩa
trong chương trình, nó có thể được gọi bên trong hàm main
. Chú ý rằng chúng ta
đã định nghĩa another_function
sau hàm main
; chúng ta có thể định nghĩa nó
trước cũng không sao cả. Rust không quan tâm bạn định nghĩa hàm của bạn ở đâu,
miễn là nó đã được định nghĩa ở đâu đó.
Chúng ta hãy bắt đầu một binary project mới, tên là functions để tìm hiểu sâu
hơn về hàm. Đặt ví dụ another_function
trong src/main.rs và chạy nó. Bạn sẽ
thấy output sau:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/functions`
Hello, world!
Another function.
Các dòng thực thi theo thứ tự chúng xuất hiện trong hàm main
. Đầu tiên, tin
nhắn “Hello, world!” in ra, sau đó another_function
được gọi và tin nhắn
của nó được hiển thị.
Tham số (Parameters)
Hàm cũng có thể được định nghĩa đi kèm với các tham số (parameter), những biến đặc biệt là một phần của hàm. Khi một hàm có tham số, bạn có thể gọi hàm với giá giá trị cụ thể cho những tham số đó. Về mặt lý thuyết, những giá trị cụ thể được gọi là đối số (argument), nhưng trong cuộc hội thoại thông thường, mọi người hay dùng lẫn lộn tham số và đối số cho cả biến trong định nghĩa hàm và giá trị cụ thể truyền vào khi gọi hàm.
Dưới đây là phiên bản viết lại của another_function
cho bạn thấy tham số trông
như thế nào trong Rust:
Filename: src/main.rs
fn main() { another_function(5); } fn another_function(x: i32) { println!("The value of x is: {}", x); }
Thử chạy chương trình này; bạn sẽ nhận được output sau:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 1.21s
Running `target/debug/functions`
The value of x is: 5
Khai báo của another_function
có một tham số tên là x
. Kiểu của x
được đặt
là i32
. Khi 5
được truyền vào another_function
, macro println!
đặt 5
ở nơi mà cặp đóng mở ngoặc nhọn được đặt trong chuỗi định dạng.
Khi khai báo hàm, bạn phải khai báo kiểu của mỗi tham số. Đây là một quyết định đã được cân nhắc kỹ trong thiết kế của Rust: yêu cầu khai báo kiểu trong định nghĩa hàm có nghĩa là trình biên dịch hầu như không bao giờ cần bạn dùng chúng ở nơi nào khác trong code để tìm ra kiểu của tham số.
Khi bạn muốn một hàm có nhiều tham số, phân cách các khai báo tham số với dấu phẩy, như sau:
Filename: src/main.rs
fn main() { print_labeled_measurement(5, 'h'); } fn print_labeled_measurement(value: i32, unit_label: char) { println!("The measurement is: {}{}", value, unit_label); }
Ví dụ trên tạo một hàm tên là print_labeled_measurement
với hai tham số. Tham số đầu tiên
tên value
có kiểu i32
. Tham số thứ hai tên unit_label
và có kiểu char
. Hàm này có
nhiệm vụ in ra value
và unit_label
.
Chúng ta hãy thử chạy đoạn code trên. Thay thế chương trình hiện tại trong file
src/main.rs của project functions và chạy lại với lệnh cargo run
:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/functions`
The measurement is: 5h
Vì chúng ta đã gọi hàm và truyền 5
là giá trị cho value
và h
là giá trị cho
unit_label
, output chương trình được in ra với những giá trị này.
Lệnh và Biểu thức (Statements and Expressions)
Thân hàm được tạo bởi một chuỗi các statement kết thúc trong một expression (không bắt buộc). Cho tới giờ, chúng ta mới chỉ đề cập đến hàm mà không có expression kết thúc, nhưng bạn đã thấy một expression như một phần của statement. Bởi vì Rust là một ngôn ngữ expression-based, đây là một điểm phân biệt quan trọng cần hiểu. Những ngôn ngữ khác không có những sự phân biệt như này, chúng ta hãy cùng xem statement và expression là gì và sự khác biệt của chúng ảnh hưởng thế nào tới thân hàm.
Thực ra chúng ta đã sử dụng statement và expression rồi. Statement là những câu lệnh thực thi một vài hành động và không trả về giá trị. Expression thì thực hiện các phép đánh giá, tính toán ra giá trị trả về. Hãy cùng nhìn vào một số ví dụ sau.
Việc tạo một biến và gán giá trị cho nó với từ khóa let
là một statement. Trong
Listing 3-1, let y=6;
là một statement.
Filename: src/main.rs
fn main() { let y = 6; }
Listing 3-1: Một khai báo hàm main
chứa một statement
Những định nghĩa hàm cũng là những statement; cả ví dụ trước chính bản thân nó là một statement.
Statement không trả về giá trị. Do đó, bạn không thể gán một statement let
cho một
biến khác, như đoạn code sau cố gắng làm; bạn sẽ gặp lỗi:
Filename: src/main.rs
fn main() {
let x = (let y = 6);
}
Khi chạy chương trình này, lỗi bạn gặp sẽ trông như sau:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
error[E0658]: `let` expressions in this position are experimental
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
= help: you can write `matches!(<expr>, <pattern>)` instead of `let <pattern> = <expr>`
error: expected expression, found statement (`let`)
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: variable declaration using `let` is a statement
warning: unnecessary parentheses around assigned value
--> src/main.rs:2:13
|
2 | let x = (let y = 6);
| ^^^^^^^^^^^ help: remove these parentheses
|
= note: `#[warn(unused_parens)]` on by default
For more information about this error, try `rustc --explain E0658`.
warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` due to 2 previous errors; 1 warning emitted
Statement let y = 6
không trả về giá trị, nên không có gì để gán cho x
. Đây
là điểm khác biệt với những gì xảy ra ở những ngôn ngữ khác, ví dụ như C và Ruby,
nơi mà phép gán trả về giá trị của phép gán. Trong những ngôn ngữ kia, bạn có thể
viết x = y = 6
và cả x
và y
có giá trị 6
; nhưng điều đó không có trong Rust.
Thử xét một phép toán đơn giản, 5 + 6
, nó là một expression được tính ra giá trị
11
. Expression có thể là một phần của statement: trong Listing 3-1, 6
trong
statement let y = 6;
là một expression đưa ra giá trị 6
. Gọi hàm cũng là một
expression. Gọi một macro cũng là một expression. Khối code mà chúng ta sử dụng
để tạo ra một vùng mới, {}
, cũng là một expression, ví dụ:
Filename: src/main.rs
fn main() { let y = { let x = 3; x + 1 }; println!("The value of y is: {}", y); }
Expression này:
{
let x = 3;
x + 1
}
là một block, mà trong trường hợp này, đưa ra giá trị 4
. Giá trị đó gán cho y
như một phần của statement let
. Chú ý dòng x + 1
không kết thúc với dấu chấm
phẩy, không giống như những dòng khác mà bạn đã thấy. Expression không kết thúc
với dấu chấm phẩy. Nếu bạn thêm một dấu chấm phẩy vào cuối expresion, nó sẽ trở
thành statement, và không trả về giá trị. Hãy ghi nhớ điều này vì bạn sẽ tiếp
tục tìm hiểu về giá trị trả về của hàm và expression.
Hàm và giá trị trả về
Hàm có thể trả về giá trị cho đoạn code gọi nó. Chúng ta không đặt tên cho giá
trị trả về, nhưng chúng ta khai báo kiểu của chúng sau một mũi tên (->
). Trong
Rust, giá trị trả về của hàm cũng chính là giá trị của expression sau cùng
trong thân hàm. Bạn có thể trả về giá trị sớm hơn bằng cách dùng return
, nhưng
ngầm định hầu hết các hàm trả về expression cuối cùng. Đây là một ví dụ về việc
một hàm trả về một giá trị:
Filename: src/main.rs
fn five() -> i32 { 5 } fn main() { let x = five(); println!("The value of x is: {}", x); }
Không có lời gọi hàm, macro hay thậm chí let
statement trong hàm five
. Hàm
như vậy hoàn toàn hợp lệ trong Rust. Kiểu trả về của hàm được khai báo với
-> i32
. Thử chạy đoạn code trên, chúng ta nhận được output như sau:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/functions`
The value of x is: 5
Số 5
trong five
là một giá trị trả về của hàm, đây là lý do cho việc kiểu
trả về là i32
. Phân tích chi tiết chi tiết hơn nữa. Có hai điểm quan trọng:
thứ nhất là dòng let x = five();
thể hiện rằng chúng ta đang sử dụng giá trị
trả về của một hàm để khởi tạo một biến. Bởi vì hàm five
trả về 5
, dòng code
đó tương đương với:
#![allow(unused)] fn main() { let x = 5; }
Thứ hai, hàm five
định nghĩa kiểu của giá trị trả về và không có tham số, thân
hàm chỉ có duy nhất số 5
đứng một mình và không đi kèm dấu chấm phẩy nào bởi
vì nó là một expression cho giá trị mà chúng ta muốn trả về.
Hãy cùng xem một ví dụ khác:
Filename: src/main.rs
fn main() { let x = plus_one(5); println!("The value of x is: {}", x); } fn plus_one(x: i32) -> i32 { x + 1 }
Chạy code này sẽ in ra The value of x is: 6
. Nhưng nếu chúng ta đặt dấu chấm
phẩy ở cuối dòng x + 1
, biến nó từ một expression thành một statement, chúng
ta sẽ gặp lỗi.
Filename: src/main.rs
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1;
}
Biên dịch đoạn code này sẽ cho lỗi như sau:
$ cargo run
Compiling functions v0.1.0 (file:///projects/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: consider removing this semicolon
For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` due to previous error
Lỗi chính là “mismatched types,” chỉ ra cốt lõi vấn đề với đoạn code này. Định
nghĩa hàm plus_one
nói rằng nó sẽ trả về một giá trị i32
, nhưng statement
thì không trả ra giá trị, thứ được biểu diễn bởi một cặp đóng mở ngoặc đơn rỗng,
()
, unit type. Do đó, không có gì được trả về, trái ngược với định nghĩa của hàm và kết
quả là lỗi. Trong output này, Rust đưa ra một tin nhắn có thể giúp sửa chữa vấn
đề này: nó gợi ý rằng bỏ đi dấu chấm phẩy thì có thể sửa được lỗi.