Tour of Rust - Ch.4 - Generic Types

Published on

เขียนวันที่ : April 24, 2022

tour-of-rust/chapter4

บันทึกการเรียน Tour Of Rust บทเรียนที่ 4 - Tour of Rust - Chapter 4 - Generic Types

Generic เป็น type ที่มีความสำคัญมากในภาษา Rust สามารถใช้แทนที่ค่า null ได้, การจัดการกับ Error case และ Collections

Chapter 4 - Generic Types 🦀

What Are Generic Types?

  • คือการกำหนด struct หรือ enum แค่บางส่วน และที่เหลือให้ compiler จัดการ
  • คือ Type ที่เรากำหนดขึ้นมาเองได้ ตอน compile-time
  • ใช้ keyword ::<T> - หรือที่เรียกกันว่า turbofish

ตัวอย่าง generic types

struct BagOfHolding<T> {
    item: T,
}
  • จะเห็นว่าเรากำหนด type เป็น T type อะไรก็ได้ และเวลาเราใช้ ขึ้นอยู่กับว่าตอนเราใช้ เรากำหนดให้ T เป็น types อะไร แบบนี้:
let i32_bag = BagOfHolding::<i32> { item: 42 };
let bool_bag = BagOfHolding::<bool> { item: true };

Representing Nothing

  • ภาษาอื่นๆ เราใช้ค่า null ได้ แต่ใน Rust ไม่มีค่า null
  • แต่มี None ให้ใช้นั่นเอง จะเห็นบ่อยๆ กับการใช้ร่วมกับ Option
  • Option เอาไว้ใช้กับค่าที่อาจจะเป็น null ก็ได้ โดยที่เราไม่ต้องใช้ null

ตัวภาษษ Rust มี generic enum ที่ชื่อ Option แบบนี้

enum Option<T> {
    None,
    Some(T),
}

เวลาที่เราสร้าง struct type แบบด้านล่าง ตัว item เราเลยเป็นได้ทั้ง None หรือ Some

struct BagOfHolding<T> {
    item: Option<T>,
}

// เป็น None
let i32_bag = BagOfHolding::<i32> { item: None };

// เป็น Some
let i32_bag = BagOfHolding::<i32> { item: Some(42) };

Result

  • คล้ายๆ Option ตัว Result ก็เป็น Generic type อีกตัวในภาษา Rust ส่วนใหญ่จะเอาไว้ handle Result, Error มีรูปแบบนี้
enum Result<T, E> {
    Ok(T),
    Err(E),
}
  • สังเกตว่า generic types ด้านบน มีทั้ง T และ E เวลาเราใช้งานจะใช้แบบนี้
fn do_something_that_might_fail(i:i32) -> Result<f32,String> {
    if i == 42 {
        Ok(13.0)
    } else {
        Err(String::from("this is not the right number"))
    }
}
  • จากโค๊ดด้านบน T คือ type f32 จะส่งผลลัพธ์ Ok(f32) ตอน success
  • และ E คือ type String จะส่ง Err(String) กลับไปตอน error
  • ตัวฟังค์ชั่น main ก็สามารถคืนค่า Result ได้เหมือนกัน
fn main() -> Result<(), String> {
    // code.
    //

    // ใช้ unit value เพื่อบอกว่า Result Ok
    Ok(())
}

Graceful Error Handling

  • เราจะเห็น Result ใช้ร่วมกับ ? บ่อยๆ ซึ่งโค๊ดด้านล่าง
do_something_that_might_fail()?

มีค่าเท่ากับการใช้ Ok และ Err แบบนี้

match do_something_that_might_fail() {
    Ok(v) => v,
    Err(e) => return Err(e),
}

ตัวโค๊ดก็จะสั้นลง เพราะเหมือน ? เป็นการรวม result Ok และจะ failed ถ้ามี Err

fn main() -> Result<(), String> {
    // Look at how much code we saved!
    let v = do_something_that_might_fail(42)?;
    println!("found {}", v);
    Ok(())
}

Ugly Option/Result Handling

  • มาดูการใช้ function ที่ชื่อว่า unwrap ที่มีใน Option และ Result
  • ตัว unwrap จะทำการดึงค่า Option หรือ Result ออกมา ถ้าเจอค่า enum None หรือ Err จะ panic!

ตัวอย่างโค๊ดจากเว็บ ที่เกิดการ panic

fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
    if i == 42 {
        Ok(13.0)
    } else {
        Err(String::from("this is not the right number"))
    }
}

fn main() -> Result<(), String> {
    // concise but assumptive and gets ugly fast
    let v = do_something_that_might_fail(42).unwrap();
    println!("found {}", v);

    // this will panic!
    let v = do_something_that_might_fail(1).unwrap();
    println!("found {}", v);

    Ok(())
}

เวลา compile จะได้ result ประมาณนี้

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "this is not the right number"', src/main.rs:15:45

Vectors

  • เป็น generic type ที่มีประโยชน์ที่สุดอีกตัวนึง และน่าจะใช้บ่อยมากๆ เพราะถ้าจะสร้าง collection ก็มักใช้ vector
  • การสร้าง vector จะใช้ macro vec!
  • ตัว Vec จะมี method iter() ที่ทำให้เราสามารถ loop ได้
  • Vec เป็นแค่ struct โดยเก็บ reference ไปยัง list ใน heap

ตัวอย่างสร้าง vector ด้วย type i32

let mut i32_vec = Vec::<i32>::new(); // turbofish <3
i32_vec.push(1);
i32_vec.push(2);
i32_vec.push(3);

สามารถสร้าง Vector แบบไม่ต้อง explicit type ก็ได้ ตัว Rust จะรู้เอง

let mut float_vec = Vec::new();
float_vec.push(1.3);
float_vec.push(2.3);
float_vec.push(3.4);

สร้างแบบ macro vec!

let string_vec = vec![String::from("Hello"), String::from("World")];
let i32_vec = vec![1, 2, 3];

// ใช้ .iter() เพื่อ loop
for word in string_vec.iter() {
    println!("{}", word);
}

สรุปบทที่ 4

บทนี้ได้เรียนรู้เรื่อง Generic type จริงๆ ถ้าเขียนภาษาอื่นๆ ที่เป็น Statically Typed ก็น่าจะเข้าใจไม่ยากครับ ว่า Generic type คืออะไร แต่อาจจะงงๆ นิดๆเกี่ยวกับพวก Result, Option, None พวกนี้ แต่ถ้าเขียนบ่อยๆ ยังไงก็ต้องใช้แน่นอน

อ่านเพิ่มเติม

Discord