[Rust Book] 3.3. Hàm

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:

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ốđố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:

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:

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 valueunit_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 valueh 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 ví dụ sau, let y=6; là một statement.

fn main() {
    let y = 6;
}

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:

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: expected expression, found statement (`let`)
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: variable declaration using `let` is a statement

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>`

warning: unnecessary parentheses around assigned value
 --> src/main.rs:2:13
  |
2 |     let x = (let y = 6);
  |             ^         ^
  |
  = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
  |
2 -     let x = (let y = 6);
2 +     let x = let y = 6;
  | 

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ả xy 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 ví dụ trước, 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ụ:

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ị:

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:

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:

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.

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.

Source

https://doc.rust-lang.org/stable/book/ch03-03-how-functions-work.html

Bài liên quan

comments powered by Disqus