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

ตัวอย่างโปรแกรมการใช้งานโครงสร้างข้อมูล Struct (An Example Program Using Structs)

เพื่อทำความเข้าใจว่าในสถานการณ์ใดที่เรามีความเหมาะสมในการดึงเอา structs มาประยุกต์ใช้งาน ลองมาเขียนโปรแกรมคณิตศาสตร์คำนวณพื้นที่รูปสี่เหลี่ยมผืนผ้าด้วยกัน โดยเราจะเริ่มดีไซน์โปรแกรมขึ้นมาจากรูปแบบการใช้ตัวแปรเดี่ยวแยกตำแหน่งกันปกติก่อน แล้วหลังจากนั้นค่อยปรับโครงสร้างโค้ด (refactor) ขยับขยายเปลี่ยนมาประกาศใช้โครงสร้าง structs เข้ามาแทนที่ตามลำดับ

ลองมาจัดตั้งโปรเจกต์รันไบนารีตัวใหม่ด้วย Cargo ชื่อว่า rectangles ซึ่งจะคอยรับค่าสเปกความกว้างและความสูงของรูปสี่เหลี่ยมผืนผ้าคิดคำนวณมีหน่วยวัดเป็นพิกเซล (pixels) และหาคำตอบพื้นที่รูปสี่เหลี่ยมดังกล่าว รายการที่ 5-8 แสดงตัวอย่างซอร์สโค้ดฉบับย่อของวิธีเขียนโปรแกรมดำเนินการสิ่งนั้นเก็บไว้ในไฟล์ src/main.rs ของโปรเจกต์เรา:

fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

คราวนี้ ทดลองสั่งประมวลผลรันโปรแกรมนี้ผ่านคำสั่ง cargo run:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/rectangles`
The area of the rectangle is 1500 square pixels.

โค้ดโปรแกรมนี้สามารถประเมินผลลัพธ์คำนวณพื้นที่สี่เหลี่ยมผืนผ้าออกมารายงานได้อย่างถูกต้องโดยการสั่งเรียกใช้งานฟังก์ชัน area ร่วมกับตัวแปรสเปกความกว้างและความสูง แต่เราสามารถปรับเปลี่ยนพัฒนาซอร์สโค้ดชุดนี้ให้อ่านทำความเข้าใจง่ายและมีความเป็นระเบียบขึ้นได้มากกว่านี้

ประเด็นปัญหาสำคัญของโค้ดชุดนี้จะปรากฏให้สังเกตเห็นได้ชัดในส่วนของสเปกลายเซ็นของฟังก์ชัน area:

fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

ฟังก์ชัน area ควรจะทำหน้าที่หาพื้นที่ของรูปสี่เหลี่ยมผืนผ้าเดี่ยว 1 รูป แต่โครงสร้างฟังก์ชันที่เราเขียนขึ้นในปัจจุบันกลับยอมรับพารามิเตอร์ประดับเข้ามาถึง 2 ค่า และในประโยคโค้ดก็ไม่ได้บ่งบอกประวัติความเชื่อมโยงความสัมพันธ์ของตัวแปรทั้งสองตัวนี้เลย การปรับวิธีเขียนขมวดเอาความกว้างและความสูงมามัดจัดกลุ่มรวมเข้าไว้ด้วยกันจะช่วยทำให้อ่านรหัสและเข้าดูแลโปรแกรมได้ง่ายกว่าเดิม ซึ่งแนวทางจัดกลุ่มข้อมูลแบบนี้เราเคยร่วมสนทนากันไปในบทที่ 3 หัวข้อ “ชนิดข้อมูลแบบทูเพิล” มาแล้วรอบหนึ่ง นั่นคือการเปลี่ยนมาใช้งานโครงสร้างทูเพิล

การปรับปรุงโครงสร้างโค้ดด้วยการใช้ทูเพิล (Refactoring with Tuples)

รายการที่ 5-9 แสดงตัวอย่างโปรแกรมอีกเวอร์ชันหนึ่งที่ปรับเปลี่ยนโครงสร้างหันมาหยิบเอาทูเพิลมาเขียนประยุกต์ใช้งานแทน:

fn main() {
    let rect1 = (30, 50);

    println!(
        "The area of the rectangle is {} square pixels.",
        area(rect1)
    );
}

fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

หากมองในแง่หนึ่ง ตัวโปรแกรมเวอร์ชันนี้จะมีความก้าวหน้าขึ้น เนื่องจากทูเพิลช่วยจัดระเบียบโครงสร้างข้อมูลได้ในระดับหนึ่ง และส่งผลให้เราเหลือส่งผ่านอาร์กิวเมนต์เข้าฟังก์ชันเพียงแค่รายการเดียวเท่านั้น แต่ถ้ามองอีกมิติตัวโค้ดชุดนี้จะสูญเสียความชัดเจนลงไปบ้าง: เนื่องจากโครงสร้างทูเพิลจะปราศจากการระบุชื่อฟิลด์กำกับให้แก่สมาชิกภายใน ส่งผลให้การเข้าอ่านเขียนต้องเขียนระบุชี้ผ่านดัชนีตัวเลขแทน เช่น ดัชนี 0 และ 1 ซึ่งส่งผลเสียทำให้สูตรคณิตศาสตร์คำนวณพื้นที่ของเราอ่านทำความเข้าใจความหมายได้ยากขึ้น

การสลับตำแหน่งค่าความกว้างและความสูงอาจไม่มีผลร้ายรุนแรงยามประมวลผลคำนวณพื้นที่สี่เหลี่ยม แต่อย่างไรก็ตามหากเป้าหมายของโปรแกรมขยายขอบเขตไปถึงการวาดเขียนสี่เหลี่ยมนั้นลงบนหน้าจอคอมพิวเตอร์ การสลับค่าดัชนีจะส่งผลร้ายทันที! โดยเราจำเป็นต้องคอยพะวงจดจำในหัวตลอดว่า width คือดัชนี 0 และ height คือดัชนี 1 ซึ่งจะเป็นภาระที่น่าหงุดหงิดใจมากสำหรับนักพัฒนาคนอื่นยามต้องเข้ามาร่วมอ่านใช้งานซอร์สโค้ดชุดนี้ และเนื่องจากตัวโค้ดโปรแกรมของเราไม่ได้ซ่อนคำอธิบายความหมายของตัวแปรข้อมูลเอาไว้เลย จึงกลายเป็นการเปิดรอยแยกโหว่ให้เกิดข้อผิดพลาดในการเขียนโปรแกรมได้ง่ายยิ่งขึ้น

การปรับปรุงโครงสร้างโค้ดด้วยการใช้สตรักต์ (Refactoring with Structs)

เราเลือกประยุกต์ใช้งาน structs เพื่อป้อนเติมคุณค่าความหมายให้แก่ตัวแปรผ่านการติดป้ายชื่อระบุกำกับข้อมูล โดยเราสามารถแปลงข้อมูลทูเพิลเดิมของโปรเจกต์ให้ขยับสภาพไปเป็นสตรักต์สำเร็จรูปที่มีชื่อหลักคุมอ็อบเจกต์รวมถึงมีการตั้งชื่อระบุให้แก่ตัวแปรฟิลด์ย่อยแต่ละองค์ประกอบอย่างมีระบบระเบียบ ดังแสดงในรายการที่ 5-10:

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        area(&rect1)
    );
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

จากโค้ดตรงจุดนี้ เราเขียนคำนิยามกำหนดโครงสร้าง struct ขึ้นมาโดยระบุตั้งชื่อหลักให้มันว่า Rectangle ภายในวงเล็บปีกกาเราประกาศปักป้ายฟิลด์ย่อยเป็น width และ height โดยกำหนดชนิดข้อมูลของทั้งคู่เป็นประเภทตัวเลข u32 หลังจากนั้นในส่วนของฟังก์ชัน main เราสร้างตัวแทน instance ของ Rectangle ขึ้นมาโดยป้อนกำหนดความกว้างเป็น 30 และความสูงเป็น 50

ฟังก์ชัน area ของเราในรอบนี้จะถูกกำหนดสเปกยอมรับพารามิเตอร์เพียงตัวเดียว โดยเราตั้งชื่อตัวแปรนั้นว่า rectangle สเปกชนิดตัวแปรประกาศเป็นประเภทการขอยืมอ้างอิงแบบแก้ไขไม่ได้ (immutable borrow) ชี้ไปหาตัวแทนอ็อบเจกต์สตรักต์ Rectangle ดังที่เราเรียนรู้กันไปในบทที่ 4 การเลือกใช้วิธียืมใช้ตัวแปรแทนที่การโอนย้ายสิทธิ์ครอบครองหน่วยความจำ จะช่วยเอื้ออำนวยให้ขอบเขตของฟังก์ชัน main ยังคงถือครองกรรมสิทธิ์ความปลอดภัยในตัวแปร rect1 สลักไว้ประมวลผลบรรทัดล่างต่อไปได้ และเป็นสาเหตุสำคัญที่เราจำเป็นต้องใส่เครื่องหมายแอมเพอร์แซนด์ & เคียงคู่ลายเซ็นฟังก์ชันและระบุแนบพ่วงในการสั่งส่งผ่านอาร์กิวเมนต์

ภายในฟังก์ชัน area จะเข้าอ่านข้อมูลฟิลด์ width และ height ออกมาจากสตรักต์ตัวแทน Rectangle (พึงสังเกตว่าการเขียนดึงข้อมูลเฉพาะฟิลด์ออกจากอ็อบเจกต์ตัวอ้างอิงของสตรักต์ จะไม่มีผลลัพธ์ทำให้เกิดการย้ายสิทธิ์ครอบครองหน่วยความจำของข้อมูลฟิลด์ตัวนั้นๆ แต่อย่างใด นี่จึงเป็นเหตุผลที่วิถีการเขียนขอยืมอ้างอิงสตรักต์จึงได้รับความนิยมอย่างแพร่หลาย) ลายเซ็นของฟังก์ชัน area ของเราจะบ่งชี้จุดประสงค์โปรแกรมอย่างแจ่มชัด: คือดำเนินการคำนวณพื้นที่ของ Rectangle ผ่านการเรียกหยิบข้อมูลในฟิลด์ width และ height สิ่งนี้สะท้อนความสัมพันธ์ว่าทั้งความกว้างและความสูงเป็นองค์ประกอบที่มัดรวมเกี่ยวเนื่องกัน และยังได้มีชื่อตัวแปรช่วยบอกกล่าวความหมายข้อมูล แทนที่จะเขียนแบบเดาใจดัชนีเลข 0 และ 1 ของทูเพิล ช่วยส่งเสริมความเคลียร์และอ่านง่ายของโค้ดโปรแกรมขึ้นมาอย่างชัดเจน

การเสริมสมรรถนะตัวแปรด้วยเทรตสืบทอด (Adding Functionality with Derived Traits)

ในขั้นตอนการสืบหาบั๊กและดีบั๊กโปรแกรม (debugging) นักพัฒนาย่อมมีความต้องการความสะดวกในการเขียนสั่งให้โปรแกรมคอยพิมพ์รายงานค่าข้อมูลตัวแทน instance ของ Rectangle ออกมาดูสเปกฟิลด์ทุกรายการ รายการที่ 5-11 จึงลองเขียนรหัสระบุสั่งเรียกมาโคร println! ทำนองเดียวกับที่เราเคยทำในบทก่อนๆ แต่ทว่าวิธีการนี้จะประสบความล้มเหลวไม่ผ่านคอมไพล์:

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {rect1}");
}

เมื่อเราสั่งคอมไพล์โค้ดโปรแกรมชิ้นนี้ เราจะได้รับข้อมูลรายงานความผิดพลาดซึ่งมีใจความหลักระบุว่า:

error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`

ตัวมาโคร println! ได้รับการตั้งสเปกให้มีความสามารถรองรับการฟอร์แมตจัดวางรูปแบบหน้าตาข้อความได้หลากหลายประการ โดยเกณฑ์มาตรฐานทั่วไปแล้ว สัญลักษณ์วงเล็บปีกกาเปล่า {} จะเป็นการส่งสัญญาณบงการบอกให้มาโครเลือกฟอร์แมตข้อมูลในรูปแบบที่เรียกว่า Display ซึ่งเป็นรูปแบบการแสดงผลแบบสะอาดสายตาที่มุ่งเน้นสำหรับการนำไปแสดงผลตรงถึงมือผู้ใช้งานทั่วไป ชนิดข้อมูลพื้นฐานทั่วไป (primitive types) ทั้งหมดที่เราเรียนรู้กันมาจะมีการแนบตราความสามารถ Display ติดตัวมาให้ตั้งแต่แรกเริ่ม เนื่องจากทิศทางการแสดงผลข้อมูลตัวเลขเช่นเลข 1 หรือข้อมูลพื้นฐานอื่นๆ จะมีทิศทางเดียวที่เดาได้ง่าย แต่พอขยับมาสเปกโครงสร้างเชิงซ้อนแบบ structs แนวทางการฟอร์แมตหน้าตาข้อความของ println! จะคลุมเครือขึ้นมากเนื่องจากมีมิติแนวทางจัดวางได้หลายแบบ: คุณต้องการให้พิมพ์คั่นด้วยเครื่องหมายจุลภาคคอมมาหรือไม่? คุณอยากให้พิมพ์วงเล็บปีกกาโชว์ขึ้นมาด้วยไหม? ควรพิมพ์แสดงเนื้อหาของฟิลด์ทุกตัวออกมาครบถ้วนหรือเปล่า? ด้วยทิศทางที่เดาเจตนายากลักษณะนี้ Rust จึงเลือกที่จะไม่พยายามสุ่มเดาใจนักพัฒนา และจัดให้โครงสร้างสตรักต์แบบแต่งเองไม่มีข้อกำหนดการทำงานของ Display ติดตัวมาให้สวมใส่กับวงเล็บปีกกา {} เปล่าใน println!

หากเราขยับสายตาลงไปอ่านข้อมูลข้อผิดพลาดถัดลงมาด้านล่างต่อ จะพบคำอธิบายช่วยแนะนำชี้ทางดังนี้:

help: the trait `std::fmt::Display` is not implemented for `Rectangle`
  --> src/main.rs:1:1

ลองทำตามคำแนะนำกันดูครับ! หน้าตาการสั่งการมาโคร println! ของเราในรอบนี้จะเปลี่ยนสเปกเป็น println!("rect1 is {rect1:?}"); การพิมพ์แทรกชุดอักษรพิเศษ :? ไว้ข้างในวงเล็บปีกกาจะเป็นการสะกิดบอกกล่าวให้ println! เลือกฟอร์แมตข้อความขากลับออกมาในรูปแบบที่เรียกว่า Debug โดยตราเทรต Debug จะคอยช่วยประมวลผลจัดการพิมพ์สตรักต์ของให้ออกมาในรูปที่เป็นมิตรและมีประโยชน์ต่อเหล่านักพัฒนา เพื่อให้เราแอบส่องดูค่าข้อมูลภายในได้สะดวกยามสืบหาจุดบกพร่องของโค้ด

สั่งคอมไพล์โค้ดอีกรอบหลังขยับรหัสเสร็จ ปัดโธ่! โปรแกรมยังคงรันไม่ผ่านและแจ้งรายงานความล้มเหลวโชว์มาอีกรอบ:

error[E0277]: `Rectangle` doesn't implement `Debug`

แต่อย่างไรก็ตาม ตัวคอมไพเลอร์ที่แสนดีก็หยิบยื่นคำแนะนำดีๆ มาประดับเพิ่มให้อีกแถว:

   |                        required by this formatting parameter
   |

Rust มี กลไกจัดทำระบบเตรียมประมวลผลข้อมูลการพิมพ์เพื่อดีบั๊ก (debugging information) ตระเตรียมไว้ให้เบื้องหลังอยู่แล้วจริง เพียงแต่สตรักต์ที่เรานิยามสร้างขึ้นมาใหม่จำเป็นต้องทำการยื่นประสงค์ขอรับรองรับสิทธิ์การใช้งานนั้นอย่างเป็นทางการก่อน วิธีสั่งการง่ายมากโดยเขียนระบุชุดตราคุณลักษณะเสริมพิเศษภายนอกในรูปคำสั่ง #[derive(Debug)] วางแปะไว้ที่บรรทัดด้านบนสุดตรงจุดเหนือนิยามความหมายของสตรักต์พอดี ดังแสดงให้เห็นในรายการที่ 5-12:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {rect1:?}");
}

ตอนนี้เมื่อเราสั่งรันโปรแกรม คอมไพเลอร์จะทำงานฉลุยไร้ข้อผิดพลาด และแสดงรายงานข้อมูลขากลับส่งตรงขึ้นมาหน้าจอคอนโซลดังนี้:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/rectangles`
rect1 is Rectangle { width: 30, height: 50 }

ยอดเยี่ยมมาก! แม้ว่าข้อมูลขากลับที่จัดหน้าพ่นออกมาจะยังไม่ได้ดูเรียบร้อยสวยงามมากนัก แต่มันแสดงสาระสเปกค่าข้อมูลในฟิลด์ทุกรายการของตัวแปรอ็อบเจกต์ตัวแทนให้ตรวจสอบดูได้ครบครัน ซึ่งช่วยเพิ่มประสิทธิภาพแก่การดีบั๊กเป็นอย่างมาก และในกรณีที่โครงสร้างสตรักต์ในระบบของคุณสะสมฟิลด์ไว้ปริมาณเยอะขึ้น การแสดงผลเป็นแถวยาวเดี่ยวอาจจะเริ่มกวาดสายตาอ่านได้ยาก คุณสามารถขยับเปลี่ยนชนิดจัดฟอร์แมตหันมาเขียนพิมพ์ในรูป {:#?} แทนสัญลักษณ์เครื่องหมาย {:?} ปกติในประโยคของ println! ได้ ซึ่งผลลัพธ์ของสไตล์ {:#?} ของโจทย์ข้อนี้จะมีพฤติกรรมแสดงรายงานข้อมูลแบบเว้นวรรคบรรทัดสวยงามดังนี้:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/rectangles`
rect1 is Rectangle {
    width: 30,
    height: 50,
}

นอกจากนี้ยังมีแนวทางอำนวยความสะดวกในการพิมพ์ตรวจสอบมูลค่าข้อมูลโดยสั่งฟอร์แมตในรูปแบบ Debug อีกหนึ่งเส้นทาง คือเรียกใช้งานมาโคร dbg! ซึ่งมาโครตัวนี้จะมีเงื่อนไขพฤติกรรมการประมวลผลคือจะโอนย้ายยึดครองสิทธิ์ (takes ownership) ของนิพจน์คำสั่งที่ป้อนเข้าไปประมวลผล (ต่างจากมาโคร println! ที่ใช้วิธีดึงตัวอ้างอิงขอยืมไปอ่านเฉยๆ) จากนั้นจะดำเนินการพ่นระบุแจ้งข้อมูลประวัติชื่อไฟล์ซอร์สโค้ดและเลขที่บรรทัดของจุดที่มีการเรียกใช้งานมาโคร dbg! โชว์ประกบขึ้นมาคู่ไปกับเนื้อหาผลลัพธ์ข้อมูลนิพจน์นั้นๆ และดำเนินการคืนสิทธิ์ครอบครอง (returns ownership) ขากลับไปให้ตัวแปรปลายทางหยิบไปใช้ต่อได้ตามครรลอง

หมายเหตุ: ขั้นตอนการประมวลผลสั่งรันมาโคร dbg! จะจัดส่งข้อความรายงานออกไปที่ช่องคอนโซลส่งผ่านข้อผิดพลาดมาตรฐาน (stderr) ซึ่งจะต่างจากพฤติกรรมทำงานของมาโคร println! ที่ประมวลผลส่งรายงานข้อความออกทางช่องรายงานผลลัพธ์มาตรฐานปกติ (stdout) เราจะได้มาร่วมศึกษารายละเอียดเชิงเทคนิคเรื่องช่องทางข้อมูล stderr และ stdout ได้ใน บทที่ 12 หัวข้อ “การเปลี่ยนทิศทางส่งผ่านข้อความไปยังคอนโซลข้อผิดพลาดมาตรฐาน”

ต่อไปนี้เป็นภาพสถานการณ์ตัวอย่างจริงยามเรามีความสนใจประสงค์ใคร่รู้ข้อมูลขากลับที่เกิดขึ้นตอนประมวลผลและต้องบันทึกลงไปในฟิลด์ width รวมถึงต้องการดีบั๊กดูสภาพอ็อบเจกต์รวมของ rect1 ไปพร้อมๆ กัน:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}

เราสามารถเขียนขีดครอบมาโคร dbg! ใส่ในนิพจน์ส่วนการประมวลผลอย่าง 30 * scale ได้อย่างอิสระ และเนื่องจากตัวมาโคร dbg! คืนกรรมสิทธิ์ความเป็นเจ้าของของผลลัพธ์ขากลับให้ปกติ ฟิลด์ width จึงได้รับค่าคณิตศาสตร์ทำงานเหมือนเสมือนไม่มีมาโคร dbg! ไปครอบไว้ตั้งแต่แรก และเนื่องจากในบรรทัดด้านล่างสุดเราปราศจากความต้องการให้ dbg! ไปยึดสิทธิ์กรรมสิทธิ์ตัวแปรหลักของ rect1 ไปถาวร เราจึงเลือกส่งผ่านเป็นตัวอ้างอิงของ rect1 ในรูป &rect1 ในการเรียกใช้งานรอบถัดไปแทน รายละเอียดหน้าตาข้อความขากลับของโค้ดชุดนี้ปรากฏดังนี้:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/rectangles`
[src/main.rs:10:16] 30 * scale = 60
[src/main.rs:14:5] &rect1 = Rectangle {
    width: 60,
    height: 50,
}

เราสามารถตรวจสอบรายงานบรรทัดแรกที่พ่นตอบขากลับมาจากจุดอ้างอิงของไฟล์ src/main.rs บรรทัดที่ 10 ซึ่งบ่งบอกพฤติกรรมวิเคราะห์สูตร 30 * scale และส่งคืนตัวเลขคำตอบคือ 60 (พฤติกรรมฟอร์แมต Debug ของเลขจำนวนเต็มปกติทั่วไปคือโชว์เฉพาะตัวเลขดิบๆ) และสำหรับตัวดีบั๊กของมาโคร dbg! บรรทัดแถวที่ 14 ของไฟล์ src/main.rs จะแสดงผลรายงานพิกัดโครงสร้างวัตถุสตรักต์ Rectangle ของข้อมูลอ้างอิง &rect1 ซึ่งการจัดหน้าข้อความขากลับนี้จะถูกสั่งรันขยับหน้าตาจัดเรียงออกมาสวยงามตามพิมพ์เขียวของ Debug ที่เราประดับใส่ไว้ให้แก่สตรักต์ Rectangle มาโคร dbg! ถือเป็นผู้ช่วยมือขวาที่แสนสะดวกยามนักพัฒนาต้องการสำรวจเส้นทางการประมวลผลแถวรหัสคำสั่งของโปรเจกต์!

นอกเหนือจากตราเทรต Debug ที่แนะนำกันไปเบื้องต้น Rust ได้จัดเตรียมตราคุณสมบัติเทรตเด่นๆ สำเร็จรูปอื่นอีกเป็นจำนวนมากให้เราดึงมาประดับสวมใส่ประยุกต์ใช้ร่วมกับคำสั่ง derive เพื่อติดป้ายมอบความสามารถเก่งๆ พ่วงเข้าใส่ชนิดข้อมูลแต่งเองของเราได้ทันที รายการเทรตพิเศษเหล่านั้นและพฤติกรรมการทำงานของพวกมันได้รับการบันทึกรวบรวมไว้ให้ศึกษาใน ภาคผนวก C เราจะมาร่วมลุยศึกษาวิธีการเขียนนิยามข้อกำหนดเทรตขึ้นมาประยุกต์ใช้งานด้วยตัวคุณเองอย่างละเอียดรวมถึงกระบวนการเขียนเทรตสร้างเองได้ในบทที่ 10 นอกจากนี้ในระบบยังมีตราสัญลักษณ์ลักษณะ (attributes) อื่นๆ อีกสารพัดประโยชน์นอกเหนือไปจาก derive สามารถศึกษาเนื้อหาเชิงลึกเพิ่มเติมต่อได้ใน หัวข้อ “คุณลักษณะประดับ (Attributes)” ของเอกสารอ้างอิงภาษา Rust (Rust Reference)

การออกแบบฟังก์ชัน area ในปัจจุบันมีข้อจำกัดอยู่จุดหนึ่งคือ: มันมีความเฉพาะเจาะจงมาก โดยประมวลผลคำนวณพื้นที่จำกัดเฉพาะได้เพียงแค่รูปสี่เหลี่ยมผี่ยมผ้าเท่านั้น จะดีกว่าอย่างแน่นอนหากเราหันมาใช้วิธีเขียนเชื่อมผูกพฤติกรรมการทำงานนี้ให้แนบชิดติดอยู่กับตัวสตรักต์ Rectangle ไปโดยตรงเลย เนื่องจากมันไม่มีประโยชน์ใช้งานกับชนิดข้อมูลประเภทอื่นๆ มาลองสำรวจขยับขั้นตอนพัฒนาปรับปรุงโค้ดกันต่อด้วยการแปลงโฉมตัวฟังก์ชัน area นอกระบบให้กลายร่างมาเป็นเมธอด (method) เฉพาะถิ่นที่สังกัดนิยามความสามารถอยู่ภายใต้ชนิดข้อมูล Rectangle กันเลยครับ