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

นิยามพฤติกรรมร่วมด้วย Trait

trait ประกาศ functionality ที่ type เฉพาะมีและแชร์กับ type อื่นได้ เรา ใช้ trait ประกาศพฤติกรรมร่วมในแบบนามธรรมได้ เราใช้ trait bound ระบุว่า generic type เป็น type ใด ๆ ที่มีพฤติกรรมเฉพาะได้

หมายเหตุ: Trait คล้ายกับฟีเจอร์ที่มักเรียกว่า interface ในภาษาอื่น แม้มีความแตกต่างบางอย่าง

ประกาศ Trait

พฤติกรรมของ type ประกอบด้วยเมธอดที่เราเรียกบน type นั้นได้ Type ต่างกัน แชร์พฤติกรรมเดียวกันถ้าเราเรียกเมธอดเดียวกันบน type เหล่านั้นได้ทั้งหมด Definition trait เป็นวิธีจัดกลุ่ม signature เมธอดเข้าด้วยกัน เพื่อประกาศ ชุดของพฤติกรรมที่จำเป็นเพื่อบรรลุจุดประสงค์บางอย่าง

เช่น สมมติเรามี struct หลายตัวที่เก็บชนิดและปริมาณข้อความต่าง ๆ — struct NewsArticle ที่เก็บข่าวสารที่ยื่นในตำแหน่งเฉพาะ และ SocialPost ที่ มีอักขระมากที่สุด 280 ตัว พร้อม metadata ที่บ่งบอกว่ามันเป็น post ใหม่, repost หรือ reply กับ post อื่น

เราอยากทำ library crate aggregator media ชื่อ aggregator ที่แสดงสรุป ข้อมูลที่อาจเก็บใน instance NewsArticle หรือ SocialPost ในการทำสิ่ง นี้ เราต้องการสรุปจากแต่ละ type และเราจะขอสรุปนั้นโดยเรียกเมธอด summarize บน instance Listing 10-12 แสดง definition ของ trait Summary public ที่แสดงพฤติกรรมนี้

Filename: src/lib.rs
pub trait Summary {
    fn summarize(&self) -> String;
}
Listing 10-12: trait Summary ที่ประกอบด้วยพฤติกรรมที่ให้โดยเมธอด summarize

ที่นี่ เราประกาศ trait โดยใช้ keyword trait แล้วชื่อ trait ซึ่งคือ Summary ในกรณีนี้ เรายังประกาศ trait เป็น pub เพื่อให้ crate ที่ พึ่ง crate นี้ใช้ trait นี้ได้ด้วย อย่างที่เราจะเห็นในตัวอย่างไม่กี่ตัว ภายใน curly bracket เราประกาศ signature เมธอดที่บรรยายพฤติกรรมของ type ที่ implement trait นี้ ซึ่งในกรณีนี้คือ fn summarize(&self) -> String

หลัง signature เมธอด แทนการให้ implementation ภายใน curly bracket เราใช้ semicolon แต่ละ type ที่ implement trait นี้ต้องให้พฤติกรรม custom ของ ตัวเองสำหรับ body ของเมธอด Compiler จะบังคับใช้ว่า type ใด ๆ ที่มี trait Summary จะมีเมธอด summarize ประกาศด้วย signature นี้เป๊ะ ๆ

Trait มีเมธอดหลายตัวใน body ได้ — signature เมธอด list หนึ่งตัวต่อบรรทัด และแต่ละบรรทัดจบด้วย semicolon

Implement Trait บน Type

ตอนนี้เราประกาศ signature ที่ต้องการของเมธอดของ trait Summary แล้ว เรา implement บน type ใน aggregator media ของเราได้ Listing 10-13 แสดง implementation ของ trait Summary บน struct NewsArticle ที่ใช้ headline, author และ location สร้าง return value ของ summarize สำหรับ struct SocialPost เราประกาศ summarize เป็น username ตามด้วย text ทั้งหมดของ post สมมติว่าเนื้อหา post จำกัดแล้วที่ 280 อักขระ

Filename: src/lib.rs
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
Listing 10-13: Implement trait Summary บน type NewsArticle และ SocialPost

การ implement trait บน type คล้ายกับการ implement เมธอดปกติ ความแตกต่าง คือหลัง impl เราใส่ชื่อ trait ที่เราอยาก implement แล้วใช้ keyword for แล้วระบุชื่อ type ที่เราอยาก implement trait ให้ ภายใน block impl เราใส่ signature เมธอดที่ definition trait ประกาศ แทนการเพิ่ม semicolon หลังแต่ละ signature เราใช้ curly bracket และเติม body เมธอด ด้วยพฤติกรรมเฉพาะที่เราอยากให้เมธอดของ trait มีสำหรับ type เฉพาะ

ตอนนี้ library implement trait Summary บน NewsArticle และ SocialPost แล้ว user ของ crate เรียกเมธอด trait บน instance ของ NewsArticle และ SocialPost ในแบบเดียวกับที่เราเรียกเมธอดปกติ ความ แตกต่างเดียวคือ user ต้องนำ trait เข้า scope เช่นเดียวกับ type นี่คือ ตัวอย่างที่ binary crate ใช้ library crate aggregator ของเรา:

use aggregator::{SocialPost, Summary};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    };

    println!("1 new post: {}", post.summarize());
}

โค้ดนี้พิมพ์ 1 new post: horse_ebooks: of course, as you probably already know, people

Crate อื่นที่พึ่ง crate aggregator ยังนำ trait Summary เข้า scope เพื่อ implement Summary บน type ของพวกเขาเองได้ ข้อจำกัดหนึ่งที่ต้อง หมายเหตุคือ เรา implement trait บน type ได้แค่ถ้า trait หรือ type หรือ ทั้งคู่ เป็น local ของ crate เรา เช่น เรา implement standard library trait อย่าง Display บน type custom อย่าง SocialPost ได้ เป็นส่วนของ functionality crate aggregator ของเรา เพราะ type SocialPost เป็น local ของ crate aggregator เรา เรายัง implement Summary บน Vec<T> ใน crate aggregator เราได้ เพราะ trait Summary เป็น local ของ crate aggregator เรา

แต่เรา implement trait ภายนอกบน type ภายนอกไม่ได้ เช่น เรา implement trait Display บน Vec<T> ภายใน crate aggregator เราไม่ได้ เพราะ Display และ Vec<T> ทั้งคู่ประกาศใน standard library และไม่ใช่ local ของ crate aggregator เรา ข้อจำกัดนี้เป็นส่วนของคุณสมบัติที่เรียกว่า coherence และเฉพาะกว่า orphan rule ที่ตั้งชื่อแบบนั้นเพราะ type พ่อ ไม่มี กฎนี้รับประกันว่าโค้ดของคนอื่นทำให้โค้ดของคุณเสียและตรงข้ามไม่ได้ ไม่มีกฎ สอง crate implement trait เดียวกันสำหรับ type เดียวกันได้ และ Rust ไม่รู้ว่าจะใช้ implementation ไหน

ใช้ Default Implementation

บางครั้งมีประโยชน์ที่จะมีพฤติกรรม default สำหรับเมธอดบางตัวหรือทั้งหมดใน trait แทนการกำหนด implementation สำหรับเมธอดทั้งหมดบนทุก type จากนั้น เมื่อเรา implement trait บน type เฉพาะ เราเก็บหรือ override พฤติกรรม default ของแต่ละเมธอดได้

ใน Listing 10-14 เราระบุ string default สำหรับเมธอด summarize ของ trait Summary แทนการประกาศแค่ signature เมธอด อย่างที่เราทำใน Listing 10-12

Filename: src/lib.rs
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
Listing 10-14: ประกาศ trait Summary ด้วย default implementation ของเมธอด summarize

ในการใช้ default implementation สรุป instance ของ NewsArticle เราระบุ block impl ว่างด้วย impl Summary for NewsArticle {}

แม้เราไม่ประกาศเมธอด summarize บน NewsArticle ตรง ๆ แล้ว เราให้ default implementation และระบุว่า NewsArticle implement trait Summary ผลคือ เรายังเรียกเมธอด summarize บน instance ของ NewsArticle ได้ ดังนี้:

use aggregator::{self, NewsArticle, Summary};

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from(
            "The Pittsburgh Penguins once again are the best \
             hockey team in the NHL.",
        ),
    };

    println!("New article available! {}", article.summarize());
}

โค้ดนี้พิมพ์ New article available! (Read more...)

การสร้าง default implementation ไม่บังคับให้เราเปลี่ยนอะไรเรื่อง implementation ของ Summary บน SocialPost ใน Listing 10-13 เหตุผลคือ syntax สำหรับ override default implementation เหมือนกับ syntax สำหรับ implement เมธอด trait ที่ไม่มี default implementation

Default implementation เรียกเมธอดอื่นใน trait เดียวกันได้ แม้เมธอดอื่น เหล่านั้นไม่มี default implementation ในวิธีนี้ trait ให้ functionality ที่มีประโยชน์เยอะและบังคับให้ผู้ implement ระบุแค่ส่วนเล็กของมัน เช่น เราประกาศ trait Summary ให้มีเมธอด summarize_author ที่บังคับ implement และจากนั้นประกาศเมธอด summarize ที่มี default implementation ที่เรียกเมธอด summarize_author:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

ในการใช้ version Summary นี้ เราแค่ต้องประกาศ summarize_author เมื่อ เรา implement trait บน type:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

หลังเราประกาศ summarize_author เราเรียก summarize บน instance ของ struct SocialPost ได้ และ default implementation ของ summarize จะ เรียก definition ของ summarize_author ที่เราให้ เพราะเรา implement summarize_author trait Summary ให้เราพฤติกรรมของเมธอด summarize โดยไม่บังคับให้เราเขียนโค้ดเพิ่ม นี่คือสิ่งที่ดูเหมือน:

use aggregator::{self, SocialPost, Summary};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    };

    println!("1 new post: {}", post.summarize());
}

โค้ดนี้พิมพ์ 1 new post: (Read more from @horse_ebooks...)

หมายเหตุว่าเป็นไปไม่ได้ที่จะเรียก default implementation จาก implementation ที่ override เมธอดเดียวกัน

ใช้ Trait เป็น Parameter

ตอนนี้คุณรู้วิธีประกาศและ implement trait แล้ว เราสำรวจวิธีใช้ trait ประกาศฟังก์ชันที่รับ type ต่างกันหลายตัวได้ เราจะใช้ trait Summary ที่ เรา implement บน type NewsArticle และ SocialPost ใน Listing 10-13 ประกาศฟังก์ชัน notify ที่เรียกเมธอด summarize บน parameter item ซึ่งเป็น type บางตัวที่ implement trait Summary ในการทำสิ่งนี้ เราใช้ syntax impl Trait ดังนี้:

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

แทน type คอนกรีตสำหรับ parameter item เราระบุ keyword impl และชื่อ trait Parameter นี้รับ type ใด ๆ ที่ implement trait ที่ระบุ ใน body ของ notify เราเรียกเมธอดใด ๆ บน item ที่มาจาก trait Summary ได้ เช่น summarize เราเรียก notify และส่ง instance ใด ๆ ของ NewsArticle หรือ SocialPost ได้ โค้ดที่เรียกฟังก์ชันด้วย type อื่นใด เช่น String หรือ i32 จะ compile ไม่ผ่าน เพราะ type เหล่านั้นไม่ implement Summary

Syntax ของ Trait Bound

syntax impl Trait ทำงานสำหรับกรณีตรงไปตรงมา แต่จริง ๆ เป็น syntax sugar ของรูปยาวที่รู้จักในชื่อ trait bound ดูแบบนี้:

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

รูปยาวกว่านี้เทียบเท่ากับตัวอย่างในส่วนก่อนหน้า แต่ยาวกว่า เราวาง trait bound พร้อมการประกาศ generic type parameter หลัง colon และภายใน angle bracket

syntax impl Trait สะดวกและทำให้โค้ดกระชับขึ้นในกรณีง่าย ขณะที่ syntax trait bound เต็มแสดงความซับซ้อนได้มากขึ้นในกรณีอื่น เช่น เรามีสอง parameter ที่ implement Summary ได้ การทำเช่นนั้นด้วย syntax impl Trait ดูแบบนี้:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

การใช้ impl Trait เหมาะถ้าเราอยากให้ฟังก์ชันนี้อนุญาตให้ item1 และ item2 มี type ต่างกัน (ตราบที่ทั้งสอง type implement Summary) ถ้าเรา อยากบังคับให้ทั้งสอง parameter มี type เดียวกัน เราต้องใช้ trait bound แบบนี้:

pub fn notify<T: Summary>(item1: &T, item2: &T) {

Generic type T ที่ระบุเป็น type ของ parameter item1 และ item2 จำกัดฟังก์ชัน ดังนั้น type คอนกรีตของค่าที่ส่งเป็น argument สำหรับ item1 และ item2 ต้องเหมือนกัน

Trait Bound หลายตัวด้วย Syntax +

เรายังระบุ trait bound มากกว่าหนึ่งตัวได้ สมมติเราอยากให้ notify ใช้ display formatting และ summarize บน item ด้วย เราระบุใน definition notify ว่า item ต้อง implement ทั้ง Display และ Summary เราทำ ได้โดยใช้ syntax +:

pub fn notify(item: &(impl Summary + Display)) {

syntax + ยัง valid กับ trait bound บน generic type:

pub fn notify<T: Summary + Display>(item: &T) {

ด้วยสอง trait bound ที่ระบุ body ของ notify เรียก summarize และใช้ {} format item ได้

Trait Bound ที่ชัดเจนขึ้นด้วย Clause where

การใช้ trait bound มากเกินไปมีข้อเสีย แต่ละ generic มี trait bound ของ ตัวเอง ดังนั้นฟังก์ชันที่มี generic type parameter หลายตัวมีข้อมูล trait bound มากระหว่างชื่อฟังก์ชันและ list parameter ทำให้ signature ฟังก์ชัน อ่านยาก ด้วยเหตุนี้ Rust มี syntax ทางเลือกสำหรับระบุ trait bound ภายใน clause where หลัง signature ฟังก์ชัน ดังนั้น แทนการเขียนแบบนี้:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

เราใช้ clause where ดังนี้:

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    unimplemented!()
}

Signature ฟังก์ชันนี้เลอะเทอะน้อยลง — ชื่อฟังก์ชัน, list parameter และ return type ใกล้กัน คล้ายกับฟังก์ชันที่ไม่มี trait bound เยอะ

Return Type ที่ Implement Trait

เรายังใช้ syntax impl Trait ในตำแหน่ง return เพื่อ return ค่าของ type บางตัวที่ implement trait ดังที่แสดงที่นี่:

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable() -> impl Summary {
    SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    }
}

โดยใช้ impl Summary สำหรับ return type เราระบุว่าฟังก์ชัน returns_summarizable return type บางตัวที่ implement trait Summary โดยไม่ตั้งชื่อ type คอนกรีต ในกรณีนี้ returns_summarizable return SocialPost แต่โค้ดที่เรียกฟังก์ชันไม่ต้องรู้

ความสามารถในการระบุ return type แค่ด้วย trait ที่มัน implement มีประโยชน์ เป็นพิเศษในบริบทของ closure และ iterator ซึ่งเราครอบคลุมในบทที่ 13 Closure และ iterator สร้าง type ที่แค่ compiler รู้ หรือ type ที่ยาวมาก ในการระบุ syntax impl Trait ให้คุณระบุแบบกระชับว่าฟังก์ชัน return type บางตัวที่ implement trait Iterator โดยไม่ต้องเขียน type ยาวมาก

อย่างไรก็ตาม คุณใช้ impl Trait ได้แค่ถ้าคุณ return type เดียว เช่น โค้ดนี้ที่ return ทั้ง NewsArticle หรือ SocialPost ที่ระบุ return type เป็น impl Summary จะไม่ทำงาน:

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from(
                "Penguins win the Stanley Cup Championship!",
            ),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        }
    } else {
        SocialPost {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            repost: false,
        }
    }
}

การ return ทั้ง NewsArticle หรือ SocialPost ไม่อนุญาตเพราะข้อจำกัด รอบวิธีที่ syntax impl Trait ถูก implement ใน compiler เราจะครอบคลุม วิธีเขียนฟังก์ชันที่มีพฤติกรรมนี้ในส่วน “ใช้ Trait Object เพื่อนามธรรมพฤติกรรมร่วม” ของบทที่ 18

ใช้ Trait Bound Implement เมธอดแบบมีเงื่อนไข

ด้วยการใช้ trait bound กับ block impl ที่ใช้ generic type parameter เรา implement เมธอดแบบมีเงื่อนไขสำหรับ type ที่ implement trait ที่ระบุ ได้ เช่น type Pair<T> ใน Listing 10-15 มี implement ฟังก์ชัน new เสมอ เพื่อ return instance ใหม่ของ Pair<T> (จำจากส่วน “Method Syntax” ของบทที่ 5 ว่า Self เป็น type alias สำหรับ type ของ block impl ซึ่งในกรณีนี้คือ Pair<T>) แต่ ใน block impl ถัดไป Pair<T> implement เมธอด cmp_display แค่ถ้า type ภายใน T implement trait PartialOrd ที่เปิดทางการเปรียบเทียบ และ trait Display ที่เปิดทางการพิมพ์

Filename: src/lib.rs
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}
Listing 10-15: Implement เมธอดบน generic type แบบมีเงื่อนไขขึ้นกับ trait bound

เรายัง implement trait แบบมีเงื่อนไขสำหรับ type ใด ๆ ที่ implement trait อื่นได้ Implementation ของ trait บน type ใด ๆ ที่ตรงตาม trait bound เรียกว่า blanket implementation และใช้กันอย่างกว้างขวางใน Rust standard library เช่น standard library implement trait ToString บน type ใด ๆ ที่ implement trait Display Block impl ใน standard library ดูคล้ายโค้ดนี้:

impl<T: Display> ToString for T {
    // --snip--
}

เพราะ standard library มี blanket implementation นี้ เราเรียกเมธอด to_string ที่ประกาศโดย trait ToString บน type ใด ๆ ที่ implement trait Display ได้ เช่น เราเปลี่ยน integer เป็นค่า String ที่สอดคล้อง ของพวกมันแบบนี้ได้ เพราะ integer implement Display:

#![allow(unused)]
fn main() {
let s = 3.to_string();
}

Blanket implementation ปรากฏใน documentation สำหรับ trait ในส่วน “Implementors”

Trait และ trait bound ให้เราเขียนโค้ดที่ใช้ generic type parameter ลด การซ้ำ แต่ยังระบุให้ compiler ว่าเราอยากให้ generic type มีพฤติกรรมเฉพาะ ด้วย Compiler ใช้ข้อมูล trait bound เช็คว่า type คอนกรีตทั้งหมดที่ใช้ กับโค้ดของเราให้พฤติกรรมที่ถูก ในภาษา dynamically typed เราจะได้ error ตอน runtime ถ้าเราเรียกเมธอดบน type ที่ไม่ประกาศเมธอด แต่ Rust ย้าย error เหล่านี้ไป compile time เพื่อให้เราถูกบังคับให้แก้ปัญหาก่อนโค้ด ของเรารันได้ด้วยซ้ำ นอกจากนี้ เราไม่ต้องเขียนโค้ดที่เช็คพฤติกรรมตอน runtime เพราะเราเช็คตอน compile time แล้ว การทำเช่นนั้นปรับปรุง performance โดยไม่ต้องเสียความยืดหยุ่นของ generic