Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ฟังก์ชัน

ฟังก์ชันมีอยู่ทั่วไปในโค้ด Rust คุณเห็นหนึ่งในฟังก์ชันที่สำคัญที่สุดในภาษา มาแล้ว — ฟังก์ชัน main ซึ่งเป็น entry point ของโปรแกรมจำนวนมาก คุณยัง เห็น keyword fn ด้วย ซึ่งให้คุณประกาศฟังก์ชันใหม่

โค้ด Rust ใช้ snake case เป็นสไตล์ convention สำหรับชื่อฟังก์ชันและตัว แปร ซึ่งใช้ตัวพิมพ์เล็กทั้งหมด และคั่นคำด้วย underscore นี่คือโปรแกรมที่มี ตัวอย่างการประกาศฟังก์ชัน:

Filename: src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

เราประกาศฟังก์ชันใน Rust โดยป้อน fn ตามด้วยชื่อฟังก์ชันและชุดวงเล็บ curly bracket บอก compiler ว่า body ของฟังก์ชันเริ่มและจบที่ไหน

เราเรียกฟังก์ชันใดก็ตามที่เราประกาศไว้ได้ โดยป้อนชื่อตามด้วยชุดวงเล็บ เพราะ another_function ถูกประกาศในโปรแกรม จึงเรียกจากภายในฟังก์ชัน main ได้ หมายเหตุว่าเราประกาศ another_function หลัง ฟังก์ชัน main ใน source code — จะประกาศก่อนก็ได้ Rust ไม่สนใจว่าคุณประกาศ ฟังก์ชันที่ไหน สนใจแค่ว่ามันถูกประกาศที่ใดที่หนึ่งใน scope ที่ผู้เรียก เห็นได้

มาเริ่มโปรเจกต์ binary ใหม่ชื่อ functions เพื่อสำรวจฟังก์ชันเพิ่ม วาง ตัวอย่าง another_function ใน src/main.rs แล้วรัน คุณควรเห็น output ต่อไปนี้:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.

บรรทัด execute ในลำดับที่ปรากฏในฟังก์ชัน main ก่อนอื่นข้อความ “Hello, world!” ถูกพิมพ์ จากนั้น another_function ถูกเรียกและข้อความของมันถูก พิมพ์

Parameter

เราประกาศฟังก์ชันให้มี parameter ได้ ซึ่งเป็นตัวแปรพิเศษที่เป็นส่วนหนึ่ง ของ signature ของฟังก์ชัน เมื่อฟังก์ชันมี parameter คุณส่งค่าจริงสำหรับ parameter เหล่านั้นได้ ทางเทคนิค ค่าจริงเรียกว่า argument แต่ในการ สนทนาทั่วไป คนมักใช้คำว่า parameter และ argument แทนกันได้ ทั้งสำหรับ ตัวแปรในการประกาศฟังก์ชัน หรือค่าจริงที่ส่งเข้าตอนเรียกฟังก์ชัน

ใน version นี้ของ another_function เราเพิ่ม parameter:

Filename: src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

ลองรันโปรแกรมนี้ คุณควรได้ output ต่อไปนี้:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/functions`
The value of x is: 5

การประกาศ another_function มี parameter หนึ่งตัวชื่อ x Type ของ x ระบุเป็น i32 เมื่อเราส่ง 5 เข้า another_function macro println! ใส่ 5 ตรงที่ curly bracket คู่ที่มี x อยู่ใน format string

ใน signature ของฟังก์ชัน คุณ ต้อง ประกาศ type ของแต่ละ parameter นี่ เป็นการตัดสินใจที่ตั้งใจในการออกแบบของ Rust — การกำหนดให้มี type annotation ในการประกาศฟังก์ชัน หมายความว่า compiler แทบไม่ต้องการให้คุณใช้มันที่อื่น ในโค้ดเพื่อหา type ที่คุณหมายถึง Compiler ยังให้ error message ที่มี ประโยชน์มากขึ้นได้ ถ้ามันรู้ว่าฟังก์ชันคาดหวัง type อะไร

เมื่อประกาศ parameter หลายตัว ให้คั่นการประกาศ parameter ด้วย comma ดังนี้:

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}");
}

ตัวอย่างนี้สร้างฟังก์ชันชื่อ print_labeled_measurement ที่มีสอง parameter parameter แรกชื่อ value และเป็น i32 ตัวที่สองชื่อ unit_label และเป็น type char ฟังก์ชันแล้วพิมพ์ข้อความที่มีทั้ง value และ unit_label

ลองรันโค้ดนี้ แทนที่โปรแกรมที่อยู่ในไฟล์ src/main.rs ของโปรเจกต์ functions ของคุณ ด้วยตัวอย่างก่อนหน้า แล้วรันด้วย cargo run:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/functions`
The measurement is: 5h

เพราะเราเรียกฟังก์ชันด้วย 5 เป็นค่าสำหรับ value และ 'h' เป็นค่า สำหรับ unit_label output ของโปรแกรมมีค่าเหล่านั้น

Statement และ Expression

Body ของฟังก์ชันประกอบด้วยชุดของ statement ที่อาจจบด้วย expression ที่ ผ่านมา ฟังก์ชันที่เราครอบคลุมยังไม่ได้รวม expression จบ แต่คุณเห็น expression เป็นส่วนหนึ่งของ statement แล้ว เพราะ Rust เป็นภาษา expression- based นี่เป็นความแตกต่างสำคัญที่ต้องเข้าใจ ภาษาอื่นไม่มีความแตกต่างเดียวกัน มาดูว่า statement และ expression คืออะไร และความแตกต่างกระทบ body ของ ฟังก์ชันยังไง

  • Statement คือคำสั่งที่ทำการกระทำบางอย่าง และไม่ return ค่า
  • Expression ประเมินเป็นค่าผลลัพธ์

มาดูตัวอย่าง

จริง ๆ แล้วเราใช้ statement และ expression มาแล้ว การสร้างตัวแปรและ assign ค่าให้มันด้วย keyword let เป็น statement ใน Listing 3-1 let y = 6; เป็น statement

Filename: src/main.rs
fn main() {
    let y = 6;
}
Listing 3-1: การประกาศฟังก์ชัน main ที่มี statement หนึ่งตัว

การประกาศฟังก์ชันก็เป็น statement เช่นกัน — ตัวอย่างก่อนหน้าทั้งหมดเป็น statement ในตัวมันเอง (อย่างที่เราจะเห็นในไม่ช้า การเรียกฟังก์ชันไม่ใช่ statement)

Statement ไม่ return ค่า ดังนั้นคุณ assign statement let ให้ตัวแปร อื่นไม่ได้ อย่างที่โค้ดต่อไปนี้พยายามทำ คุณจะได้ error:

Filename: src/main.rs

fn main() {
    let x = (let y = 6);
}

เมื่อคุณรันโปรแกรมนี้ error ที่คุณจะได้หน้าตาประมาณนี้:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^
  |
  = note: only supported directly in conditions of `if` and `while` expressions

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

warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted

statement let y = 6 ไม่ return ค่า จึงไม่มีอะไรให้ x bind ด้วย นี่ต่าง จากที่เกิดในภาษาอื่น เช่น C และ Ruby ที่การ assign return ค่าของการ assign ในภาษาเหล่านั้น คุณเขียน x = y = 6 แล้วทั้ง x และ y มีค่า 6 ได้ — ไม่ใช่กรณีนั้นใน Rust

Expression ประเมินเป็นค่า และประกอบเป็นโค้ดส่วนใหญ่ที่คุณจะเขียนใน Rust พิจารณา operation คณิตศาสตร์ เช่น 5 + 6 ซึ่งเป็น expression ที่ประเมิน เป็นค่า 11 Expression เป็นส่วนหนึ่งของ statement ได้ — ใน Listing 3-1 6 ใน statement let y = 6; เป็น expression ที่ประเมินเป็นค่า 6 การ เรียกฟังก์ชันเป็น expression การเรียก macro เป็น expression block scope ใหม่ที่สร้างด้วย curly bracket เป็น expression เช่น:

Filename: src/main.rs

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}");
}

Expression นี้:

{
    let x = 3;
    x + 1
}

เป็น block ที่ในกรณีนี้ประเมินเป็น 4 ค่านั้นถูก bind กับ y เป็นส่วน หนึ่งของ statement let หมายเหตุว่าบรรทัด x + 1 ไม่มี semicolon ที่ ท้าย ซึ่งต่างจากบรรทัดส่วนใหญ่ที่คุณเห็นมา Expression ไม่รวม semicolon ปิดท้าย ถ้าคุณเพิ่ม semicolon ที่ท้าย expression คุณเปลี่ยนมันเป็น statement แล้วมันจะไม่ return ค่า จำเรื่องนี้ไว้ขณะที่คุณสำรวจ return value ของฟังก์ชันและ expression ต่อไป

ฟังก์ชันที่มี Return Value

ฟังก์ชัน return ค่าให้โค้ดที่เรียกได้ เราไม่ตั้งชื่อ return value แต่ต้อง ประกาศ type หลังลูกศร (->) ใน Rust return value ของฟังก์ชันเทียบเท่ากับ ค่าของ expression สุดท้ายใน block ของ body ของฟังก์ชัน คุณ return ก่อนได้ โดยใช้ keyword return และระบุค่า แต่ฟังก์ชันส่วนใหญ่ return expression สุดท้ายแบบ implicit นี่คือตัวอย่างฟังก์ชันที่ return ค่า:

Filename: src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

ไม่มีการเรียกฟังก์ชัน, macro หรือแม้แต่ statement let ในฟังก์ชัน five — แค่ตัวเลข 5 ตัวเดียว นั่นเป็นฟังก์ชันที่ valid อย่างสมบูรณ์ใน Rust หมายเหตุว่า return type ของฟังก์ชันก็ระบุไว้ด้วย เป็น -> i32 ลองรันโค้ด นี้ output ควรหน้าตาเป็นแบบนี้:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/functions`
The value of x is: 5

5 ใน five เป็น return value ของฟังก์ชัน นี่คือเหตุผลที่ return type เป็น i32 มาตรวจสอบเรื่องนี้ในรายละเอียดเพิ่มเติม มีสองจุดสำคัญ — ประการ แรก บรรทัด let x = five(); แสดงว่าเราใช้ return value ของฟังก์ชัน initialize ตัวแปร เพราะฟังก์ชัน five return 5 บรรทัดนั้นเหมือนกับ ต่อไปนี้:

#![allow(unused)]
fn main() {
let x = 5;
}

ประการที่สอง ฟังก์ชัน five ไม่มี parameter และประกาศ type ของ return value แต่ body ของฟังก์ชันเป็น 5 เดียวดายโดยไม่มี semicolon เพราะมัน เป็น expression ที่เราอยาก return ค่ามัน

มาดูตัวอย่างอีกตัวอย่างหนึ่ง:

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
}

การรันโค้ดนี้จะพิมพ์ The value of x is: 6 แต่จะเกิดอะไรขึ้นถ้าเราวาง semicolon ที่ท้ายบรรทัดที่มี x + 1 เปลี่ยนมันจาก expression เป็น statement?

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;
}

การ compile โค้ดนี้จะ produce error ดังนี้:

$ 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: remove this semicolon to return this value

For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` (bin "functions") due to 1 previous error

Error message หลัก mismatched types เผยปัญหาแกนกลางของโค้ดนี้ การประกาศ ฟังก์ชัน plus_one บอกว่ามันจะ return i32 แต่ statement ไม่ประเมินเป็น ค่า ซึ่งแสดงด้วย () ที่เป็น unit type ดังนั้นไม่มีอะไรถูก return ซึ่ง ขัดกับการประกาศฟังก์ชัน และส่งผลให้เกิด error ใน output นี้ Rust ให้ ข้อความที่อาจช่วยแก้ปัญหานี้ — มันแนะนำให้ลบ semicolon ซึ่งจะแก้ error