Developer Logs

Day15 - ความดีงาม? ของ Rust

Experiment
Rust
Impression

Experiment... มาลองเปิดโลกใหม่กับ Rust

Days: 15 | Publish : 20/04/2025

python developer meets Rust
              

วันนี้ขอมาเป็นแนวสรุปความประทับใจ หลังจากได้ลองเขียน Rust แบบคร่าวๆ เรียกว่าคร่าวแบบ แตะ ๆ พอให้รู้ฟีลลิ่งในการเขียนโดยจะยังไม่ได้ลงอะไรลึกมากสักเท่าไร

ในฐานะที่งานของตัวเอง จะใช้ python เป็นหลัก และอยากจะเปิดโลกใหม่ ๆ ให้ตัวเองบ้าง ก็เลยลองจับ Rust มาเขียนเล่น ๆ ดู เพื่อให้รู้จักวิธีการเขียนที่ตัวเองไม่คุ้นเคยดูบ้าง

โดย codebase ที่ได้ลองก็เป็น Project เริ่มต้นที่เชื่อว่าใครหลายคนที่เริ่มต้นกับการเขียนโปรแกรมแทบจะทุกภาษา จะต้องเคยได้ลองเขียนสิ่งนี้กัน


นั่นก็คือ ลองเขียน mini game ทายตัวเลขนั่นเอง ซึ่งโจทย์ของเกมนี้ก็ simple แบบสุด ๆ 1. สุ่มตัวเลขมาตัวนึงเพื่อเป็นเลขสำหรับการทาย 2. รอรับตัวเลข จาก user input 3. เอาตัวเลขไป compare เพื่อจะบอก User ว่ามากไปหรือน้อยไป 4. ถ้าตัวเลขตรงกันก็จะขึ้นข้อความ "You win" และจบเกม​


เริ่ม

จะขอข้ามส่วนของการ build project แบบ fast forward ไปเลยแล้วกันนะครับ เรามาดูในส่วนของไฟล์หลักของตัวโปรแกรมกัน ซึ่งในการเขียนนี้ ผมเขียนโดยเอาส่วนประกอบทั้งหมดมา​ pack อยู่ในไฟล์เดียวกันคือ main.rs แค่ไฟล์เดียว​

main.rs


  use std::io;
  use std::cmp::Ordering;
  use rand::Rng;

  fn main() {
      println!("Guess the number!");

      let secret_number = rand::thread_rng().gen_range(1..=100);

      println!("The secret number is: {secret_number}");

      loop {
          println!("Please input your guess.");

          let mut guess = String::new();

          io::stdin()
              .read_line(&mut guess)
              .expect("Failed to read line");

          let guess: u32 = match guess
              .trim()
              .parse() {
                  Ok(num) => num,
                  Err(_) => {
                      println!("Please enter a valid number.");
                      continue;
                  }
              };

          println!("You guessed: {guess}");

          match guess.cmp(&secret_number) {
              Ordering::Less => println!("Too small!"),
              Ordering::Greater => println!("Too big!"),
              Ordering::Equal => {
                  println!("You win!");
                  break;
              }
          }
      }
  }

ถ้าใครลองเอาไฟล์ main นี้ไปรันดู ก็จะได้ mini game ขึ้นมาทันที​ 1 เกมไปเลย ทีนี้ ขอมาเจาะแต่ละส่วน ว่ามีส่วนไหนที่ประทับใจกับการลองเขียนครั้งนี้บ้าง

ความประทับใจที่ 1 : การประกาศตัวแปร

ส่วนแรกที่รู้สึกว่ามันเจ๋งดี คือการประกาศตัวแปร

  let secret_number = rand::thread_rng().gen_range(1..=100);
  .
  .
  let mut guess = String::new();

จะเห็นได้ว่า 2 ส่วนหลักที่แตกต่างกันระหว่าง 2 บรรทัดนี้คือ let mut กับ let ซึ่งการประกาศตัวแปรของ Rust จะมี default behavior เป็นตัวแปรแบบ immutable = ตัวแปรที่ไม่สามารถเปลี่ยนแปลงค่าได้ แต่ถ้าเราต้องการประกาศตัวแปรที่สามารถเปลี่ยนแปลงค่าได้ระหว่าง runtime (ซึ่งในที่นี้ คือ ตัวแปรที่ใช้รับ user input) เราจะใช้การประกาศตัวแปรที่จะระบุไปว่าเป็น แบบ mutable ซึ่งจะประกาศเป็นแบบนี้ let mut guess = String::new(); ก็จะเป็นการประกาศตัวแปร guess เป็น String ที่สามารถเปลี่ยนแปลงค่าได้ระหว่าง runtime


ความประทับใจที่ 2 : Error handling

การทำ error handling ที่ถือว่าดีงามมาก ๆ เป็นการดักให้ dev ไม่เขียนหลุดไปได้ ถ้าไม่มีการทำ error handling เอาไว้


  io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read line");
  .
  .
  let guess: u32 = match guess
    .trim()
    .parse() {
    Ok(num) => num,
    Err(_) => {
      println!("Please enter a valid number.");
      continue;
    }
    };

จากโค้ดชุดนี้ จะเห็นว่าเป็นการรอรับ input จาก user และก็นำไป trim และแปลงค่าให้เป็นตัวเลข เพื่อจะนำไปใช้ในการ compare ต่อ

ซึ่งจะเห็นได้ว่า มีโค้ดส่วนที่เป็น .expect() อยู่ ในส่วนนี้ เราเป็นการเรียก function .read_line() ซึ่ง Rust compiler จะบังคับเราให้มีการทำ error handling เอาไว้ด้วย เพราะถ้าเราไม่ได้เขียน ‘expect()’ เอาไว้ compiler ก็จะขึ้น​ warning มาให้เราแบบนี้

17 | /         io::stdin()
18 | |             .read_line(&mut guess);
   | |__________________________________^
   |
   = note: this `Result` may be an `Err` variant, which should be handled
   = note: `#[warn(unused_must_use)]` on by default

ซึ่งก่อนอื่นเราต้องมาเข้าใจกันก่อน ว่าฟังก์ชันจะมีการ return ได้ 2 รูปแบบ คือ Ok และ Err

โดย Compiler จะบังคับให้เราต้องทำ Error handling เอาไว้ แม้เราจะมั่นใจมากแค่ไหนก็ตามว่า User จะไม่กรอกข้อมูลในส่วนนี้มาผิด

ส่วนนี้ถ้าเทียบกับ python โค้ดในส่วนนี้ก็จะสามารถรันต่อไปได้แม้จะไม่ได้ดัก error ไว้ก็ตามที

ซึ่งเราจะรอดจาก compiler ไปไม่ได้ในกรณีนี้… นับเป็นอีกหนึ่งความดีงามที่ประทับใจจจ


ความประทับใจ(??)ที่ 3 : u32 !??

u32 = The 32-bit unsigned integer type โอ้โหหหหหหห เจอการประกาศตัวแปรแบบนี้เข้าไป ถึงกับอึ้งไปเลย 5555 ปกติจะเป็นการประกาศตัวแปรแบบ int, float, double อันนี้นี่มาเหนือสุด ๆ แต่เอาจริง ๆ ก็ทั้งรู้สึกว่าประทับใจ ปนกับรู้สึกว่ามันเข้าใจยากไปมั้ย สำหรับคนที่เพิ่งมาเขียนโปรแกรม?

แต่ข้อดีของมันก็คือมันโคตรจะตรงไปตรงมาจัด ๆ Unsigned 32 bit —> u32 คือถ้าอย่างเป็น ​int, float เราต้องมาจำเอาเองอีกว่ามัน limit ที่กี่ bit การประกาศตัวแปรของ Rust เรียกได้ว่าจบเลย ไม่ต้องรู้อะไรเพิ่มอีกแล้ว แค่จำให้ได้ก็พอว่าต้องประกาศด้วย keyword อะไร 555


ความประทับใจที่ 4 : cargo doc

ถ้าใครพอจะมี project ตัวอย่าง หรือเคยลองเขียนมาบ้าง ลองไปรันคำสั่งนี้ดูที่ project path


 cargo doc --open

โหหหห ผมตื่นเต้นกับสิ่งนี้มาก ตัว compiler จะ generate webpage ขึ้นมา และแจกแจงทุก ๆ dependencies ที่เราใช้อยู่ใน project นั้น ๆ เป็น doc refernce แบบไม่ต้องไปนั่งหาเลยว่า dependencies ที่เราเรียกใช้ใน project มีวิธีการเขียนยังไง หรือต้องรับ-ส่ง ตัวแปรอะไรบ้าง

อันนี้คือโคตรจะดีงามจริง ๆ จะมาบอกว่าหา doc reference ไม่เจอไม่ได้แล้วนะ งานนี้


ก็ประมาณนี้ครับ กับการลองเขียน Rust แบบเตาะแตะแรกเริ่มของผม ไว้มีประเด็นอะไรที่ได้เรียนรู้เพิ่มเติม แล้วจะยกมาคุยกันต่อนะครับผมมม

>_JV