diff --git a/astro.config.mjs b/astro.config.mjs index 1af6e79..dd93fc0 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -28,7 +28,10 @@ export default defineConfig({ './src/components/content/GitCommand.astro', './src/components/content/Image.astro', './src/components/content/InfoBox.astro', + './src/components/content/NarrativeSection.astro', './src/components/content/ProsCons.astro', + './src/components/content/PullQuote.astro', + './src/components/content/SideNote.astro', './src/components/content/Table.astro', './src/components/content/YouTube.astro', diff --git a/src/components/content/NarrativeSection.astro b/src/components/content/NarrativeSection.astro new file mode 100644 index 0000000..c39bc49 --- /dev/null +++ b/src/components/content/NarrativeSection.astro @@ -0,0 +1,90 @@ +--- +/** + * NarrativeSection - Wrapper for flowing prose content + * Enhanced typography for comfortable reading experience + */ +--- + +
+ +
+ + diff --git a/src/components/content/PullQuote.astro b/src/components/content/PullQuote.astro new file mode 100644 index 0000000..43a9dc1 --- /dev/null +++ b/src/components/content/PullQuote.astro @@ -0,0 +1,96 @@ +--- +/** + * PullQuote - Highlights key statements or memorable quotes + * Centered, prominent typography for emphasis + */ +--- + +
+ +
+ + diff --git a/src/components/content/SideNote.astro b/src/components/content/SideNote.astro new file mode 100644 index 0000000..8b2a141 --- /dev/null +++ b/src/components/content/SideNote.astro @@ -0,0 +1,117 @@ +--- +/** + * SideNote - Insight Block (Redesigned Iteration 2) + * Style: "Insight Block" - Minimalist, Inline, Premium + * No floats, no heavy boxes. Focus on typography and clean separation. + */ +import { Icon } from 'astro-icon/components'; + +type Props = { + type?: 'note' | 'tip' | 'warning'; +}; + +const { type = 'note' } = Astro.props; + +const icons = { + note: 'heroicons:information-circle', + tip: 'heroicons:light-bulb', + warning: 'heroicons:exclamation-triangle', +}; +--- + + + + diff --git a/src/content/blog/rust-ownership-memory-management/index.mdx b/src/content/blog/rust-ownership-memory-management/index.mdx index ba35051..206a111 100644 --- a/src/content/blog/rust-ownership-memory-management/index.mdx +++ b/src/content/blog/rust-ownership-memory-management/index.mdx @@ -8,40 +8,43 @@ tags: ['Rust', 'Memory Management', 'Ownership', 'Systems Programming', 'Backend featured: true --- -ในโลกของการจัดการหน่วยความจำ เรามักถูกบีบให้เลือกสองทางสายหลัก: ทางแรกคือความรวดเร็วแต่เสี่ยงภัยแบบ Manual Allocation ใน C/C++ และทางที่สองคือความปลอดภัยแต่แลกด้วย Runtime Overhead ของ Garbage Collector (GC) ทว่า Rust เลือกที่จะบุกเบิก **"ทางที่สาม"** นั่นคือ **Ownership System** ซึ่งเป็นชุดกฎที่คอมไพเลอร์ใช้ตรวจสอบความปลอดภัยของหน่วยความจำโดยไม่มีค่าใช้จ่ายในขณะโปรแกรมทำงาน (Zero-cost abstraction) + ---- +ในโลกของการเขียนโปรแกรมระดับ Systems Programming เรามักจะถูกบีบให้เลือกเพียงหนึ่งในสองทางที่ดูเหมือนตรงข้ามกันโดยสิ้นเชิง ทางแรกคือโลกของ C และ C++ ที่มอบความเร็วและอำนาจควบคุมเบ็ดเสร็จ แต่แลกมาด้วยความเสี่ยงของ memory leaks, dangling pointers และ buffer overflows ที่อาจกลายเป็นฝันร้ายในวันที่โปรแกรมของคุณไป production ส่วนทางที่สองคือโลกของ Garbage Collector ใน Java หรือ Go ที่ช่วยจัดการหน่วยความจำให้โดยอัตโนมัติ แต่ต้องแลกกับ runtime overhead และ latency ที่คาดเดาไม่ได้ -## จุดเริ่มต้นของตรรกะ: เมื่อ Stack และ Heap กำหนดพฤติกรรมของภาษา + +Zero-cost abstraction หมายความว่าคุณไม่ต้องจ่ายค่าใช้จ่ายใดๆ ในขณะ runtime สำหรับ safety features ที่คอมไพเลอร์มอบให้ + -ความเข้าใจใน Ownership ต้องเริ่มจากความต่างของ **Stack** และ **Heap** ในเชิงลึก +Rust ปฏิเสธที่จะเลือก มันบุกเบิก **"ทางที่สาม"** ขึ้นมาเอง นั่นคือสิ่งที่เรียกว่า **Ownership System** ซึ่งเป็นชุดกฎที่คอมไพเลอร์ใช้ตรวจสอบความปลอดภัยของหน่วยความจำตั้งแต่ขั้นตอน compile time โดยไม่มีค่าใช้จ่ายใดๆ ในขณะที่โปรแกรมทำงาน - - - ข้อมูลบน Stack มีข้อกำหนดตายตัวคือต้องทราบขนาดที่แน่นอน ณ Compile-time การทำงานแบบ LIFO (Last In, First Out) ทำให้การจองพื้นที่นั้นรวดเร็วและมีประสิทธิภาพสูงในระดับ Processor Cache - - - ถูกออกแบบมาเพื่อความยืดหยุ่นสำหรับข้อมูลที่ขนาดเปลี่ยนแปลงได้ เช่น String แต่ความยืดหยุ่นนี้มาพร้อมกับ Cost ของการทำ Allocation และ Pointer Chasing ที่ส่งผลต่อ Latency ของระบบ - - +## Stack และ Heap: รากฐานของทุกสิ่ง -**Ownership จึงถูกสร้างมาเพื่อทำหน้าที่สำคัญที่สุด:** การจัดการข้อมูลบน Heap อย่างเป็นระบบโดยที่โปรแกรมเมอร์ไม่ต้องสั่ง `free` เองแม้แต่บรรทัดเดียว +การจะเข้าใจ Ownership อย่างแท้จริง เราต้องย้อนกลับไปดูพื้นฐานที่สำคัญที่สุดเสียก่อน นั่นคือความแตกต่างระหว่าง **Stack** และ **Heap** ---- +ลองนึกภาพว่า Stack เป็นเหมือนกองจานในร้านอาหาร คุณวางจานใหม่ลงด้านบน และหยิบจานออกก็จากด้านบนเช่นกัน เป็นระบบ LIFO (Last In, First Out) ที่เรียบง่ายและรวดเร็ว ข้อมูลบน Stack ต้องมีขนาดที่แน่นอน ณ เวลาที่คอมไพล์ ไม่ว่าจะเป็น `i32`, `bool`, หรือ `char` ทุกอย่างรู้ขนาดล่วงหน้าทั้งหมด ความเรียบง่ายนี้ทำให้ Stack ทำงานได้เร็วมาก เพราะ CPU สามารถเข้าถึงได้โดยตรงผ่าน stack pointer + + +Heap ต้องทำ allocation ซึ่งหมายถึงการขอพื้นที่จาก OS และการทำ pointer chasing ที่ส่งผลต่อ cache performance + + +ในทางตรงกันข้าม Heap ถูกออกแบบมาเพื่อรองรับความยืดหยุ่น เมื่อคุณสร้าง `String` ที่ความยาวอาจเปลี่ยนแปลงได้ตลอดเวลา หรือ `Vec` ที่ไม่รู้ว่าจะมีกี่ element Heap คือคำตอบ แต่ความยืดหยุ่นนี้มาพร้อมกับราคา การจองพื้นที่บน Heap ช้ากว่า Stack และการเข้าถึงข้อมูลต้องผ่าน pointer ทุกครั้ง + +นี่คือเหตุผลว่าทำไม Ownership จึงถูกสร้างมา — เพื่อจัดการข้อมูลบน Heap อย่างเป็นระบบโดยที่คุณไม่ต้องเรียก `malloc` และ `free` เองแม้แต่บรรทัดเดียว -## Move Semantics: ทางออกของ Double Free ในระดับโครงสร้างหน่วยความจำ +## กฎเหล็กสามข้อ -หัวใจสำคัญของ Ownership สรุปได้ด้วย **กฎเหล็ก** เพียงไม่กี่ข้อ: +หัวใจของ Ownership สรุปได้ด้วยกฎเพียงสามข้อ ข้อแรก ทุก value ใน Rust ต้องมี **owner เพียงหนึ่งเดียวเท่านั้น** ไม่มีข้อยกเว้น ไม่มีการแชร์ ownership ข้อที่สอง เมื่อ owner หลุดออกจาก scope ข้อมูลนั้นจะ **ถูกทำลายทันที** โดยอัตโนมัติ ไม่ต้องรอ garbage collector มาเก็บ และข้อสุดท้าย ณ เวลาใดๆ จะมีได้เพียง **หนึ่ง mutable reference** หรือ **หลาย immutable references** เท่านั้น ไม่มีทางมีทั้งสองอย่างพร้อมกัน - - 1. ทุก Value ต้องมี **Owner เพียงหนึ่งเดียว** - 2. เมื่อ Owner หลุดออกจาก Scope ข้อมูลนั้นจะ **ถูกทำลายทันที** (RAII - Resource Acquisition Is Initialization) - 3. ณ เวลาใดๆ จะมีได้เพียง **หนึ่ง mutable reference** หรือ **หลาย immutable references** เท่านั้น - + +ทุก value ต้องมี owner เพียงหนึ่งเดียว และเมื่อ owner หลุดจาก scope ข้อมูลจะถูกทำลายทันที + -### โครงสร้างของ `String` ใน Memory +กฎเหล่านี้อาจฟังดูเข้มงวด แต่มันคือรากฐานที่ทำให้ Rust สามารถรับประกัน memory safety ได้โดยไม่ต้องพึ่ง garbage collector -เพื่อให้เห็นภาพชัดเจน เราต้องพิจารณาโครงสร้างของ `String` ซึ่งประกอบด้วย **3 ส่วนบน Stack**: +## โครงสร้างของ String และปัญหา Double Free + +เพื่อให้เห็นภาพชัดเจนยิ่งขึ้น มาดูกันว่า `String` ใน Rust มีโครงสร้างอย่างไร เมื่อคุณสร้าง `String::from("hello")` สิ่งที่เกิดขึ้นจริงๆ คือมีข้อมูลสามส่วนถูกเก็บบน Stack ได้แก่ **pointer** ที่ชี้ไปยังข้อมูลจริงบน Heap, **length** ที่บอกความยาวปัจจุบัน และ **capacity** ที่บอกพื้นที่ที่จองไว้ ``` Stack Heap @@ -54,29 +57,26 @@ Stack Heap └─────────────────┘ ``` -- **Pointer**: ชี้ไปยังข้อมูลจริงบน Heap -- **Length**: ความยาวของข้อมูลปัจจุบัน -- **Capacity**: พื้นที่ที่จองไว้บน Heap - -### ปัญหา Double Free และวิธีแก้ด้วย Move +ทีนี้ลองนึกดูว่าถ้าเราทำแบบนี้ในภาษาที่ไม่มี Ownership: - ```rust -fn main() { - let s1 = String::from("hello"); - let s2 = s1; // ในภาษาอื่นอาจเป็น Shallow Copy - - // ถ้า s1 และ s2 ชี้ไปที่ Heap เดียวกัน - // เมื่อทั้งคู่หลุดจาก scope จะเกิด Double Free! -} +let s1 = String::from("hello"); +let s2 = s1; // ในภาษาอื่นอาจเป็น Shallow Copy + +// ถ้า s1 และ s2 ชี้ไปที่ Heap เดียวกัน +// เมื่อทั้งคู่หลุดจาก scope จะเกิด Double Free! ``` - -เมื่อเราทำ Assignment เช่น `let s2 = s1;` ในภาษาทั่วไปอาจเป็นการทำ **Shallow Copy** (คัดลอกเฉพาะ Pointer) ซึ่งจะนำไปสู่หายนะที่เรียกว่า **Double Free Error** เมื่อทั้ง `s1` และ `s2` พยายามคืนหน่วยความจำที่จุดเดียวกันเมื่อจบ Scope + +Double Free คือหายนะ เพราะการคืนหน่วยความจำที่ถูกคืนไปแล้ว อาจทำให้เกิด memory corruption หรือ security vulnerabilities + + +ในภาษาทั่วไปที่ทำ shallow copy ทั้ง `s1` และ `s2` จะชี้ไปที่ข้อมูลเดียวกันบน Heap และเมื่อทั้งสองตัวหลุดจาก scope พร้อมกัน ทั้งคู่จะพยายามคืนหน่วยความจำที่จุดเดียวกัน นี่คือ **Double Free Error** ที่อาจนำไปสู่ crash หรือแย่กว่านั้นคือ security vulnerabilities -แต่ Rust แก้ปัญหานี้ด้วยการ **"Move"**: +## Move Semantics: วิธีแก้ปัญหาอย่างสง่างาม + +Rust แก้ปัญหานี้ด้วยวิธีที่เรียบง่ายแต่ทรงพลัง — มันใช้ **Move Semantics** แทนที่จะ copy pointer ทั้งสองตัว Rust จะ "ย้าย" ownership จาก `s1` ไป `s2` และทำให้ `s1` ใช้งานไม่ได้อีกต่อไป - ```rust fn main() { let s1 = String::from("hello"); @@ -86,13 +86,13 @@ fn main() { println!("{}", s2); // ✅ OK: s2 เป็น owner ตัวเดียวแล้ว } ``` - -การที่คอมไพเลอร์ **ปฏิเสธไม่ให้เราเข้าถึง `s1` ได้อีก** เป็นการการันตี Memory Safety ในระดับทฤษฎีโดยไม่ต้องเสีย Runtime ในการทำ Deep Copy +การที่คอมไพเลอร์ **ปฏิเสธไม่ให้เราเข้าถึง `s1` ได้อีก** อาจรู้สึกเหมือนข้อจำกัด แต่มันคือการป้องกัน Double Free ในระดับทฤษฎี โดยไม่ต้องเสีย runtime ในการตรวจสอบหรือทำ deep copy + +## เมื่อคุณต้องการสำเนาจริงๆ -### เลือก Clone เมื่อต้องการสำเนาจริงๆ +แน่นอนว่ามีบางครั้งที่คุณต้องการสำเนาข้อมูลจริงๆ ไม่ใช่แค่ย้าย ownership ในกรณีนี้ Rust มี method `.clone()` ให้ใช้ - ```rust fn main() { let s1 = String::from("hello"); @@ -102,32 +102,26 @@ fn main() { println!("s2 = {}", s2); // ✅ OK: s2 เป็น owner ของ copy ใหม่ } ``` - - - การเรียก .clone() หมายความว่าเรายอมแลก Performance เพื่อแลกกับข้อมูลชุดใหม่บน Heap ใช้เมื่อจำเป็นจริงๆ เท่านั้น - + +การเรียก `.clone()` หมายความว่าคุณยอมแลก performance เพื่อข้อมูลชุดใหม่บน Heap ใช้เมื่อจำเป็นจริงๆ เท่านั้น + ---- +สิ่งสำคัญคือ `.clone()` เป็น **explicit** คุณต้องเขียนมันออกมาอย่างชัดเจน ทำให้โค้ดบอกได้ทันทีว่า "ตรงนี้มี performance cost" ไม่ใช่ซ่อนไว้เหมือนภาษาอื่นที่ทำ deep copy โดยอัตโนมัติ -## Deep Dive: Drop และ Copy Trait +## Drop และ Copy: สองด้านของเหรียญเดียวกัน -Rust ใช้รูปแบบที่คล้ายกับ **RAII** ใน C++ ผ่านฟังก์ชันพิเศษที่ชื่อว่า `drop` ซึ่งจะถูกเรียกโดยอัตโนมัติที่ closing curly bracket (`}`) ของ Scope นั้นๆ +Rust ใช้รูปแบบที่คล้ายกับ RAII ใน C++ ผ่านฟังก์ชันพิเศษที่ชื่อว่า `drop` ซึ่งจะถูกเรียกโดยอัตโนมัติที่ closing curly bracket ของ scope นั้นๆ นี่คือวิธีที่ Rust ทำให้การจัดการหน่วยความจำเป็นเรื่องที่คาดเดาได้ (deterministic) - ```rust fn main() { let s = String::from("hello"); // ใช้งาน s ที่นี่... } // <- ณ จุดนี้ drop() ถูกเรียกโดยอัตโนมัติ หน่วยความจำถูกคืน ``` - - -### Copy Trait: ข้อยกเว้นสำหรับ Stack-only Data -ไม่ใช่ทุก Type ที่จะใช้พฤติกรรม Move เสมอไป ข้อมูลที่เป็น **Scalar Values** ที่ทราบขนาดแน่นอนบน Stack จะถูกจัดการผ่าน **Copy Trait**: +แต่ไม่ใช่ทุก type ที่จะใช้พฤติกรรม Move เหมือน String สำหรับข้อมูลที่เป็น scalar values ที่อยู่บน Stack อย่าง integers, floats, booleans และ chars Rust ใช้ **Copy Trait** แทน - ```rust fn main() { let x: i32 = 5; @@ -137,43 +131,17 @@ fn main() { println!("y = {}", y); // ✅ OK } ``` - - -Types ที่ implement `Copy` Trait: - - - - i8, i16, i32, i64, i128, isize
- u8, u16, u32, u64, u128, usize -
- - f32, f64 - - - bool (true / false)
- char (Unicode scalar value) -
- - Tuples ที่ทุก element เป็น Copy
- เช่น (i32, bool) แต่ไม่ใช่ (i32, String) -
-
- -### กฎที่สำคัญ: Drop และ Copy ไม่สามารถอยู่ร่วมกันได้ - - - Rust จะไม่อนุญาตให้ Type ใดๆ ที่มีส่วนประกอบของ Drop (เช่น String) มา Implement Copy Trait ได้ เพราะนั่นจะทำให้เกิดความขัดแย้งในตรรกะการจัดการ Heap ทันที - - -นี่คือการใช้ **Type System** เข้ามาบังคับใช้กฎของ Ownership ให้มีความเข้มงวดและคาดเดาผลลัพธ์ได้ (Deterministic) ---- + +Stack-only types ใช้ Copy, Heap types ใช้ Move + Drop — ทั้งสองไม่มีทาง overlap กันได้ + + +ที่น่าสนใจคือ Rust **ไม่อนุญาตให้ type ใดๆ ที่มี Drop** มา implement Copy Trait ได้ เพราะนั่นจะสร้างความขัดแย้งในตรรกะการจัดการ Heap นี่คือการใช้ Type System เข้ามาบังคับใช้กฎของ Ownership อย่างเข้มงวด -## Ownership ใน Function Boundaries +## Ownership ข้าม Function Boundaries -การส่งค่าผ่าน Function call ก็เปรียบเสมือนการทำ Assignment: +การส่งค่าผ่าน function call ก็เปรียบเสมือนการทำ assignment เช่นกัน เมื่อคุณส่ง `String` เข้าไปใน function ownership จะถูก move เข้าไปด้วย - ```rust fn main() { let s = String::from("hello"); @@ -194,37 +162,13 @@ fn makes_copy(some_integer: i32) { println!("{}", some_integer); } ``` - -### Pattern ที่ไม่สะดวก: Return Ownership กลับ +ถ้า function ไม่ส่งคืน ownership กลับมา ข้อมูลนั้นจะถูก `drop` ทันทีที่ function ทำงานเสร็จ แม้ว่ากลไกนี้จะช่วยป้องกัน memory leak ได้อย่างเด็ดขาด แต่การต้องมานั่ง return ค่ากลับคืนในรูปแบบ tuple ตลอดเวลานั้นก็เทอะทะเกินไป -หากฟังก์ชันนั้นไม่ส่งคืน Ownership กลับมา (Return) ข้อมูลนั้นจะถูก `drop` ทันทีที่ฟังก์ชันทำงานเสร็จ +## References และ Borrowing: ทางออกที่สง่างาม - -```rust -fn main() { - let s1 = String::from("hello"); - let (s2, len) = calculate_length(s1); - - println!("'{}' มีความยาว {} ตัวอักษร", s2, len); -} +ความท้าทายข้างต้นนำไปสู่ feature ที่เรียกว่า **References & Borrowing** เพื่อให้เราสามารถเข้าถึงข้อมูลได้โดยไม่ต้องถือครอง ownership -fn calculate_length(s: String) -> (String, usize) { - let length = s.len(); - (s, length) // คืน ownership กลับพร้อม result -} -``` - - -แม้ว่ากลไกนี้จะช่วยป้องกัน Memory Leak ได้อย่างเด็ดขาด แต่ในเชิงปฏิบัติการต้องมานั่ง Return ค่ากลับคืนในรูปแบบ Tuple ตลอดเวลานั้นเป็นเรื่องที่ **เทอะทะและมีพิธีรีตองเกินไป** - ---- - -## จุดกำเนิดของ References & Borrowing - -ความท้าทายข้างต้นนำไปสู่ฟีเจอร์ที่เรียกว่า **References & Borrowing** เพื่อให้เราสามารถเข้าถึงข้อมูลได้โดยไม่ต้องถือครอง Ownership - - ```rust fn main() { let s1 = String::from("hello"); @@ -238,32 +182,9 @@ fn calculate_length(s: &String) -> usize { // รับ reference s.len() } // s หลุดจาก scope แต่เพราะไม่ได้เป็น owner จึงไม่มีอะไรถูก drop ``` - - -### กฎของ References - - - - - - - - - - - - - - - - - - - - -
ชนิดSyntaxสามารถแก้ไขได้?จำนวนที่มีได้พร้อมกัน
Immutable Reference&T❌ ไม่ได้ไม่จำกัด
Mutable Reference&mut T✅ ได้1 อันเท่านั้น
- - + +การ "ยืม" (borrowing) มาในสองรูปแบบ **Immutable reference** (`&T`) ยืมมาอ่านได้อย่างเดียว แต่ยืมได้หลายคนพร้อมกัน ส่วน **Mutable reference** (`&mut T`) ยืมมาแก้ไขได้ แต่ต้องยืมคนเดียวเท่านั้น + ```rust fn main() { let mut s = String::from("hello"); @@ -275,35 +196,23 @@ fn change(some_string: &mut String) { some_string.push_str(", world"); } ``` - ---- - -## บทสรุปสำหรับ Senior Dev + +กฎของ References ถูกออกแบบมาเพื่อป้องกัน Data Race ตั้งแต่ compile time + - - Ownership ไม่ใช่แค่เรื่องของการจัดการหน่วยความจำ แต่มันคือการสร้าง "สัญญา" ระหว่าง Code แต่ละส่วน ว่าใครมีสิทธิ์ทำลายหรือใครมีสิทธิ์ใช้ข้อมูล - +## บทสรุป -### Key Takeaways +Ownership ไม่ใช่แค่เรื่องของการจัดการหน่วยความจำ แต่มันคือการสร้าง **"สัญญา"** ระหว่าง code แต่ละส่วน ว่าใครมีสิทธิ์ทำลายหรือใครมีสิทธิ์ใช้ข้อมูล - - - Memory safety ถูกตรวจสอบ ณ compile-time ไม่มี runtime overhead เหมือน GC - - - การ assign หรือส่งค่าใน function = การโอนย้าย ownership ป้องกัน double free - - - Stack-only types ใช้ Copy, Heap types ใช้ Move + Drop ไม่มีทาง overlap กัน - - - References ช่วยให้เข้าถึงข้อมูลโดยไม่ต้องโอน ownership ลด overhead ของการ clone - - + +Ownership คือ contract ระหว่าง Code — ใครมีสิทธิ์ทำลาย ใครมีสิทธิ์ใช้ ทุกอย่างถูกกำหนดไว้ชัดเจน ณ compile time + -การทำความเข้าใจ **Move Semantics** และ **Scope** อย่างถ่องแท้ จะทำให้เราเขียนโปรแกรมที่ปราศจาก **Data Race** และ **Memory Corruption** ได้อย่างมั่นใจ ซึ่งเป็นสิ่งที่ภาษาอื่นทำได้ยากหากไม่มีระบบ Ownership เข้ามาควบคุมตั้งแต่ระดับรากฐาน +สิ่งที่ควรจดจำคือ memory safety ถูกตรวจสอบ ณ compile-time ไม่มี runtime overhead เหมือน GC การ assign หรือส่งค่าใน function คือการโอนย้าย ownership ซึ่งป้องกัน double free ได้โดยธรรมชาติ Stack-only types ใช้ Copy ส่วน Heap types ใช้ Move + Drop โดยไม่มีทาง overlap กัน และ References ช่วยให้เข้าถึงข้อมูลโดยไม่ต้องโอน ownership ลด overhead ของการ clone ---- +การทำความเข้าใจ Move Semantics และ Scope อย่างถ่องแท้ จะทำให้เราเขียนโปรแกรมที่ปราศจาก Data Race และ Memory Corruption ได้อย่างมั่นใจ ซึ่งเป็นสิ่งที่ภาษาอื่นทำได้ยากหากไม่มีระบบ Ownership เข้ามาควบคุมตั้งแต่ระดับรากฐาน ในบทความถัดไป เราจะเจาะลึกเรื่อง **Lifetimes** ซึ่งเป็นอีกหนึ่งกลไกสำคัญที่ทำให้ Rust สามารถรับประกันความปลอดภัยของ References ได้อย่างสมบูรณ์แบบ + +
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 46a378a..035a079 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -2,14 +2,19 @@ import Navbar from '@/components/layout/Navbar.astro'; import ProgressBar from '@/components/ui/ProgressBar.astro'; import '@/styles/global.css'; +import '@/styles/terminal.css'; type Props = { title: string; description?: string; }; -const { title, description = 'A developer\'s living knowledge base, curated by a pharmacist turned programmer.' } = Astro.props; +const { + title, + description = 'A developer\'s living knowledge base, curated by a pharmacist turned programmer.', +} = Astro.props; const currentYear = new Date().getFullYear(); --- + @@ -31,12 +36,21 @@ const currentYear = new Date().getFullYear();