สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
Rust คืออะไร
Rust เป็นภาษาโปรแกรมระบบที่พัฒนาขึ้นโดย Mozilla ซึ่งเป็นภาษาที่เน้นความปลอดภัยด้านหน่วยความจำและประสิทธิภาพสูง เหมาะสำหรับการเขียนโปรแกรมที่ต้องการจัดการกับหน่วยความจำแบบละเอียด เช่น การพัฒนา embedded systems, web assembly หรือระบบที่ต้องการประสิทธิภาพสูงแบบ native application
โดย Key features ใหญ่ๆของ Rust มีดังนี้
- Memory Safety: Rust มีระบบการจัดการหน่วยความจำที่ปลอดภัย โดยใช้ “Ownership” และ “Borrowing” เพื่อป้องกันปัญหาที่พบบ่อยในภาษาอื่นๆ เช่น null pointer dereferencing หรือ data race ซึ่งช่วยให้ code มีความปลอดภัยโดยไม่จำเป็นต้องใช้ Garbage Collector (GC) ได้
- Zero-Cost Abstractions: Rust อนุญาตให้เราใช้ abstraction (การสร้าง Layer ของ code เพื่อให้ใช้งานง่ายและอ่านได้ง่ายขึ้น เช่น iterators และ generics) โดยไม่ต้องเสียประสิทธิภาพของการทำงานลงไป (เนื่องจาก Rust จะทำการ compile ให้มีประสิทธิภาพเทียบเท่ากับ low-level constructs) ซึ่งต่างจากบางภาษา เช่น Python หรือ JavaScript ที่ abstraction ระดับสูงอาจทำให้ code ทำงานช้าลงเพราะมีค่าใช้จ่ายในการแปลง abstraction เหล่านั้นให้กลายเป็น code ที่เครื่องอ่านได้
- Concurrency: Rust มีการจัดการ concurrency ที่ปลอดภัยจากปัญหาข้อมูลทับซ้อน (data race) โดยการใช้ ownership system เพื่อทำให้ code ที่เกี่ยวข้องกับ multithreading มีความปลอดภัยในการเข้าถึงข้อมูลพร้อมกัน
ทีนี้ Rust เองก็ได้เตรียม Resource มาให้เราได้เรียนรู้ไว้แล้วเช่นกัน ซึ่งในหัวข้อนี้เราก็จะมาลองเรียน Rust ตาม Rust Book กันโลดด (หลายเนื้อหาสามารถอ่านเพิ่มเติมจาก Rust Book ได้เช่นกัน เรียกว่าเขียนไว้ละเอียดยิบแล้วเรียบร้อย)
https://doc.rust-lang.org/book/title-page.html
โดยเพื่อให้เกิดความเข้าใจที่ถ่องแท้ในแต่ละประเด็น เราจะขอแบ่ง Rust ออกเป็น 2 Part ใหญ่ๆ (ทั้งในบทความนี้และฉบับ video) คือ
- Basic 1 = พื้นฐานทั่วไปทางภาษาของ Rust (ทำให้เห็นภาพก่อนว่าภาษามันอารมณ์ไหน)
- Basic 2 = เพิ่มคุณสมบัติพิเศษที่ทำให้ Rust implement ในรูปแบบต่างๆได้
Start Rust
https://doc.rust-lang.org/book/ch01-02-hello-world.html
เรามาเริ่มต้น hello world โดยการลองลง Rust กัน โดยสามารถลงผ่าน command นี้ได้
เมื่อลงเรียบร้อย ลอง run คำสั่ง rustc
เพื่อตรวจสอบว่าสามารถใช้คำสั่ง compiler Rust ได้แล้วหรือไม่
ถ้าได้ผลแบบนี้ = ลงเสร็จแล้วเรียบร้อย
ลองสร้าง file main.rs
แล้วเพิ่ม code นี้เข้าไป
ลอง run Rust ผ่าน command
ก็จะได้ผลลัพธ์ของ Rust ออกมาได้ = เท่านี้ก็ confirm ได้ว่าลง Rust เรียบร้อย และสามารถ compile code ได้ ไม่มีปัญหาอะไร
Note
- file ที่ได้ออกมาคือ executable file คือไฟล์ที่บรรจุคำสั่งในรูปแบบ machine code ซึ่งจำเป็นสำหรับการรันโปรแกรม
- Rust เป็นภาษาที่ใช้ การคอมไพล์ล่วงหน้า (ahead-of-time compiled language) หมายความว่า code จะถูก compile เป็น machine code ก่อนที่จะรันโปรแกรมจริง ซึ่งแตกต่างจากภาษา interpreted languages เช่น Ruby, Python หรือ JavaScript ซึ่ง code จะถูกตีความ (interpreted) และ run ทีละบรรทัดในระหว่าง runtime
Step ต่อมาเราจะลองใช้ Cargo new project กัน
รู้จักกับ Cargo
https://doc.rust-lang.org/book/ch01-03-hello-cargo.html
Cargo คือเครื่องมือ package manager และระบบ build สำหรับ Rust ที่ช่วยให้การสร้าง จัดการ dependencies และการทดสอบเป็นเรื่องง่ายขึ้น ในขณะที่ rustc
(Rust compiler) เป็นตัว compile ที่แปลง code จาก Rust ไปเป็น binary file
ข้อดีของการใช้ Cargo
- จัดการโครงการและสร้างโครงสร้างพื้นฐานอัตโนมัติ
- จัดการ dependencies ได้ง่ายและอัตโนมัติ
- Build และ run code ได้ในขั้นตอนเดียว
- รองรับการทดสอบด้วยระบบในตัว
- จัดการหลายเวอร์ชันของโปรเจกต์และ dependencies
เราสามารถเริ่ม project โดยการ run command cargo ได้ (ตัว cargo ได้ทำการลงไปพร้อมกันกับตอน rustc แล้วเรียบร้อย)
ผล command
เมื่อเข้าไปใน folder ที่สร้าวขึ้นมา ตัว cargo ก็จะ start project มาด้วย structure เริ่มต้นตามนี้
อย่างที่เห็นว่า มันจะมี file Cargo.toml ซึ่งเป็น file รวม dependency (ถ้าใครที่มาจากสาย javascript มันจะอารมณ์เหมือนๆกับ package.json
) และจุดเริ่มต้นของ Rust จะเริ่มต้นที่ src/main.rs
คำสั่งสำหรับ run ด้วย cargo
ก็จะได้ผลลัพธ์ที่เหมือนกันออกมาได้ โดยสามารถลง package ได้ผ่านคำสั่ง cargo install
เช่น เราจะลองลง library ที่สามารถ watch file ได้ (file เปลี่ยน = build ใหม่ให้ auto)
ผลลัพธ์
Common Programming Concept
https://doc.rust-lang.org/book/ch03-00-common-programming-concepts.html
เรามาเริ่มพื้นฐานแรกของ Rust กันนั่นคือ Core Programming Concept ที่มีอยู่ในทุกภาษาอย่าง Variable, Data Type, Control Flow กันว่า ภาษา Rust มีหลักการพื้นฐานทางภาษาอย่างไรบ้าง
Variable
Variable ใน Rust คือค่าที่ใช้เก็บข้อมูล โดยค่าในตัวแปรสามารถเปลี่ยนแปลงได้หากเราระบุให้ตัวแปรนั้นเป็น mutable (mut
) มิฉะนั้นตัวแปรจะถือว่าเป็น “immutable (ค่าคงที่) โดยค่าเริ่มต้น”
โดย เหตุผลที่ Rust ใช้ immutable เป็นค่าเริ่มต้น นั้น เกิดจาก concept ของ Rust ที่กำหนดไว้ว่า
- เพิ่มความปลอดภัยของ code ป้องกันไม่ให้โปรแกรมทำการเปลี่ยนแปลงค่าของตัวแปรโดยไม่ตั้งใจ ซึ่งอาจทำให้เกิดข้อผิดพลาดที่ยากจะตรวจพบ
- ป้องกันการเข้าถึงข้อมูลพร้อมกันที่ไม่ปลอดภัย (Data Race) โดย Rust ให้ความสำคัญกับการเขียน code ที่รองรับการทำงานพร้อมกัน (concurrency) การใช้ตัวแปร immutable ทำให้มั่นใจได้ว่าหลาย ๆ thread สามารถอ่านค่าของตัวแปรได้พร้อมกัน โดยไม่ต้องกังวลว่าจะมีการเปลี่ยนแปลงค่าจาก thread อื่น
ถ้างั้น คำถามก็คือ สำหรับ Rust แล้ว “เมื่อไรที่ควรใช้ตัวแปรแบบ mutable ?”
ในกรณีที่เราต้องการเปลี่ยนค่าของตัวแปร เช่น การเก็บค่าใน loop หรือตัวแปรที่ต้องอัพเดตค่าใน runtime คุณสามารถใช้ mut
เพื่อระบุว่าตัวแปรนั้นสามารถเปลี่ยนแปลงค่าได้
นี่คือตัวอย่างการประกาศ Variable แบบ Immutable และ Mutable
- ตัวแปรแบบ Immutable
- ตัวแปรแบบ Mutable (เปลี่ยนค่าได้)
Data Type
Data Type ใน Rust หมายถึงประเภทของข้อมูลที่ใช้เก็บค่าในตัวแปร ซึ่งเป็นสิ่งที่ช่วยกำหนดประเภทของข้อมูล เช่น ตัวเลข, ตัวอักษร, Boolean ฯลฯ Rust มีการจัดการประเภทข้อมูลอย่างเข้มงวด ซึ่งช่วยเพิ่มความปลอดภัยและประสิทธิภาพ
ประเภทของตัวแปร ใน Rust มีการแบ่งในแต่ละกลุ่ม โดยจะมี 2 กลุ่มหลักๆคือ
- Scalar Types (ประเภทข้อมูลเดี่ยว) เป็นประเภทข้อมูลที่แทนค่าหนึ่งค่า Rust มี 4 ชนิด คือ Integers, Floating-Point Numbers, Booleans, และ Characters
- ตัวแปรชนิด
Integer
(จำนวนเต็ม) = Rust รองรับตัวแปร integer หลายประเภท เช่นi32
,u8
,i64
เป็นต้น
- ตัวแปรชนิด
Floating Point
(จำนวนทศนิยม) เช่นf32
,f64
- ตัวแปรชนิด
Boolean
- ตัวแปรชนิด
Character
- Compound Types ประเภทข้อมูลที่ใช้จัดกลุ่มค่าหลาย ๆ ค่าเข้าด้วยกัน Rust มี Compound Types หลัก 2 ประเภท คือ Tuples และ Arrays
- ตัวแปรชนิด
Tuple
= กลุ่มของค่า “หลายประเภท” สามารถเก็บค่าหลายตัวพร้อมกัน
- ตัวแปรชนิด
Array
= เก็บค่าหลายค่าในลำดับเดียวกัน โดยต้องเป็น “ประเภทเดียวกัน”
นอกจากประเภทข้อมูลพื้นฐานที่กล่าวมา Rust ยังมีประเภทข้อมูลเพิ่มเติมที่มาจาก Standard Library ซึ่งมักถูกใช้ในโปรแกรม เช่น Strings, Slice และ Vectors
- String Types ประเภทข้อความสำหรับเก็บอักขระหลายตัว ประเภท
String
มาจาก Standard Library ของ Rust ไม่ได้ถูกเขียนในภาษาโดยตรง
String
เป็นประเภทข้อความที่ Growable (สามารถเพิ่มความยาวได้), Mutable (เปลี่ยนแปลงได้) และรองรับการเข้ารหัสแบบ UTF-8 ซึ่งเหมาะสำหรับการจัดเก็บข้อความที่ไม่สามารถทราบความยาวได้ในขณะ Compile Time
- &str (String Slice) เป็นการอ้างอิง String แบบความยาวคงที่ ไม่สามารถเปลี่ยนค่าได้ เช่น
- Slice (การอ้างอิงบางส่วนของ array) Slice เป็นการอ้างอิงบางส่วนของข้อมูล เช่นการอ้างอิงช่วงของ array
- Vector ใช้สำหรับจัดเก็บค่าหลายค่าที่สามารถปรับเปลี่ยนจำนวนได้
- โดย
Vec<T>
เป็นประเภทข้อมูลใน Standard Library ที่รองรับค่าทุกประเภท หาก Vector ใดเก็บค่าประเภทเฉพาะ จะต้องระบุประเภทข้อมูลนั้นภายในเครื่องหมาย< >
ตัวอย่างเช่นVec<i32>
หมายถึง Vector ที่เก็บค่าเฉพาะประเภทi32
Operator
Operator ใน Rust คือสัญลักษณ์ที่ใช้ในการดำเนินการกับตัวแปรหรือค่าต่าง ๆ เพื่อทำการคำนวณหรือตรวจสอบบางอย่าง เช่น การบวก การเปรียบเทียบ หรือการตรรกะ เป็นต้น Rust มีหลายประเภทของ operators เพื่อจัดการกับข้อมูลหลากหลายประเภท
ประเภทของ Operators ใน Rust
- Arithmetic Operators (ตัวดำเนินการทางคณิตศาสตร์) ใช้ในการคำนวณทางคณิตศาสตร์ระหว่างตัวเลข
+
: บวก-
: ลบ*
: คูณ/
: หาร%
: หารเอาเศษ (modulo)
- Comparison Operators (ตัวดำเนินการเปรียบเทียบ) ใช้เปรียบเทียบค่าระหว่างตัวแปรหรือค่าต่าง ๆ และให้ผลลัพธ์เป็น
true
หรือfalse
==
: เท่ากับ!=
: ไม่เท่ากับ>
: มากกว่า<
: น้อยกว่า>=
: มากกว่าหรือเท่ากับ<=
: น้อยกว่าหรือเท่ากับ
- Logical Operators (ตัวดำเนินการทางตรรกะ) ใช้ในการทำงานกับค่าบูลีน (
true
หรือfalse
)
&&
: และ (AND)||
: หรือ (OR)!
: ไม่ (NOT)
- Bitwise Operators (ตัวดำเนินการระดับบิต) ใช้ในการทำงานกับข้อมูลในระดับบิต
&
: AND บิต|
: OR บิต^
: XOR บิต<<
: เลื่อนบิตไปทางซ้าย>>
: เลื่อนบิตไปทางขวา
- Assignment Operators (ตัวดำเนินการกำหนดค่า) ใช้ในการกำหนดค่าลงในตัวแปร
=
: กำหนดค่า+=
: บวกและกำหนดค่า=
: ลบและกำหนดค่า=
: คูณและกำหนดค่า/=
: หารและกำหนดค่า%=
: หารเอาเศษและกำหนดค่า
- Range Operators (ตัวดำเนินการช่วง) ใช้ในการกำหนดช่วงตัวเลข
..
: ไม่รวมค่าปลายทาง..=
: รวมค่าปลายทาง
Function
Function ใน Rust คือกลุ่มของ code ที่ถูกรวมไว้ด้วยกันเพื่อทำงานบางอย่าง โดยสามารถรับค่า input (parameter) และส่งผลลัพธ์ออกมาได้ (return value) การใช้ function ช่วยในการจัดระเบียบ code ลดการทำงานซ้ำ และทำให้ code อ่านง่ายขึ้น
การประกาศ function ใน Rust ใช้คำสั่ง fn
ตามด้วยชื่อ function ตามด้วยพารามิเตอร์ (ถ้ามี) และประเภทของค่าที่จะส่งกลับ (ถ้ามี)
fn
: คำสั่งที่ใช้ในการประกาศ function- **ชื่อ function **: ต้องเป็นตัวพิมพ์เล็กและใช้ underscore สำหรับการแบ่งคำ เช่น
calculate_sum
- Parameters : รับค่าเข้ามาใน function กำหนดชนิดของข้อมูลให้ชัดเจน
> return_type
: ประเภทของค่าที่ function จะส่งกลับ (ถ้ามี)- ค่าที่จะส่งกลับ: ค่าที่ถูกส่งออกจาก function สามารถระบุได้ในบรรทัดสุดท้ายของ function โดยไม่ต้องใช้คำสั่ง
return
(ในบรรทัดสุดท้ายไม่จำเป็นต้องใส่;
)
ตัวอย่างในการประกาศและการใช้งาน Function
- Function ไม่มีพารามิเตอร์และไม่ส่งค่ากลับ
- Function รับพารามิเตอร์
- Function ที่มีการส่งค่ากลับ
- Function ที่มีหลายพารามิเตอร์และส่งค่ากลับ
- Function ที่ไม่มีค่าที่ส่งกลับ (ใช้
()
หรือ “unit”) หาก function ไม่ส่งค่ากลับ function นั้นจะมีประเภทการส่งกลับเป็น()
หรือที่เรียกว่า “unit type” ใน Rust
คุณสมบัติของ Function ใน Rust
- ความปลอดภัยในหน่วยความจำ: Rust ตรวจสอบความถูกต้องของการส่งคืนค่าและการจัดการหน่วยความจำ ทำให้มั่นใจได้ว่า function ทำงานได้อย่างถูกต้อง
- ไม่มีการจัดการค่าที่เป็น Null: Rust ไม่อนุญาตให้มี
null
ในการส่งค่ากลับจาก function ซึ่งช่วยลดข้อผิดพลาดจากการใช้ค่าที่เป็น Null - สามารถใช้ function แบบ Recursion ได้: function สามารถเรียกตัวเองซ้ำได้ (recursive functions) เพื่อแก้ปัญหาบางประเภทที่ซับซ้อน
Control Flow
Control Flow ใน Rust เป็นโครงสร้างที่ช่วยให้โปรแกรมของคุณตัดสินใจเลือกแนวทางการทำงานตามเงื่อนไขต่าง ๆ ตัวอย่างที่นิยมใช้คือ if
, else if
, else
และ match
โดยแต่ละโครงสร้างมีรูปแบบการใช้งานแตกต่างกันเล็กน้อย
if
,else if
, และelse
จาก code นี้ โปรแกรมจะตรวจสอบว่า number
มีค่าเท่าไร ถ้า number < 5
จะพิมพ์ข้อความแรก ถ้าเท่ากับ 5 จะพิมพ์ข้อความที่สอง และถ้ามากกว่า 5 จะพิมพ์ข้อความสุดท้าย
match
เป็นโครงสร้างควบคุมที่ทรงพลังใน Rust มันทำงานคล้ายกับswitch
ในภาษาอื่น ๆ แต่สามารถจัดการเงื่อนไขได้หลากหลายแบบมากกว่า
match
จะตรวจสอบค่า number
และดำเนินการตามเงื่อนไขที่ตรงกัน ถ้าไม่ตรงกับกรณีใด ๆ จะใช้ _
เป็นตัวแทนของ “ค่าอื่น ๆ” และพิมพ์ข้อความสุดท้าย
รวมถึง ใน Rust เองเราสามารถใช้ match
เพื่อ return ค่ากลับมาจากแต่ละเงื่อนไขได้ แล้วนำค่าที่ return มาเก็บไว้ในตัวแปรใหม่ เช่น
Loop
loop เป็นคำสั่งที่ช่วยให้เราทำการวนซ้ำ (loop) การทำงานใน แนกำ โดยมีรูปแบบการใช้งานที่หลากหลาย เช่น loop
, while
, และ for
แต่ละแบบมีจุดประสงค์ที่แตกต่างกันเล็กน้อย
loop
เป็นการวนซ้ำอย่างไม่สิ้นสุดจนกว่าจะมีคำสั่งbreak
เพื่อออกจากลูป
ผลลัพธ์
while
ทำการวนลูปจนกว่าเงื่อนไขที่กำหนดจะเป็นเท็จ
ผลลัพธ์
for
ใช้เพื่อวนลูปผ่านค่าหรือช่วงของตัวเลข
ผลลัพธ์
Ownership, Borrowing, and References
https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
ทีนี้ หลายคนก็อาจจะสงสัยว่า “Garbage Collector คืออะไร และทำไม Rust ไม่ต้องใช้ Garbage Collector ได้”
- Garbage Collector (GC) คือโปรแกรมที่ทำหน้าที่จัดการการจัดสรรและคืนหน่วยความจำในภาษาโปรแกรมต่าง ๆ โดยในภาษาที่มี GC นักพัฒนาไม่จำเป็นต้องจัดการคืนหน่วยความจำเองหลังจากใช้งานเสร็จ เนื่องจาก GC จะทำการระบุและเรียกคืนหน่วยความจำที่ไม่ได้ใช้งานโดยอัตโนมัติ ซึ่งช่วยป้องกันปัญหา memory leaks (การสูญเสียหน่วยความจำโดยไม่จำเป็น)
- แต่ อย่างไรก็ตาม Rust ไม่มีระบบ Garbage Collector แต่ใช้กลไกที่เรียกว่า ownership ซึ่งเป็นคุณสมบัติเฉพาะตัวของ Rust ที่ช่วยรับประกันความปลอดภัยของหน่วยความจำโดยไม่ต้องพึ่งพา GC
- การทำงานของระบบ Ownership ใน Rust จะมีไอเดียหลักประมาณนี้
- แต่ละค่าจะมีตัวแปรหนึ่งตัวที่เป็นเจ้าของ (owner)
- ค่าหนึ่ง ๆ สามารถมี owner ได้เพียงหนึ่งตัวเท่านั้นในเวลาใดเวลาหนึ่ง
- เมื่อ owner หลุดออกจาก scope ค่านั้นจะถูกจัดการ (dropped)
- เมื่อรวมกับกฎการยืมหน่วยความจำ (borrowing rules) ระบบนี้ช่วยป้องกันปัญหาต่าง ๆ เช่น dangling pointers (พอยน์เตอร์ที่ชี้ไปยังหน่วยความจำที่ไม่ได้ใช้งานแล้ว) และ data races (การเข้าถึงข้อมูลพร้อมกันโดยไม่ปลอดภัย) เอาไว้ได้
ระบบ Ownership, Borrowing, และ References เป็นกลไกหลักที่จัดการหน่วยความจำ (memory) เพื่อให้แน่ใจว่าโปรแกรมจะปลอดภัยจากปัญหาต่าง ๆ เช่น null pointer หรือ data race โดยไม่จำเป็นต้องใช้ garbage collector แนวคิดเหล่านี้ช่วยให้ Rust มีประสิทธิภาพในการจัดการหน่วยความจำขณะที่ยังคงความปลอดภัยในเวลาคอมไพล์ (compile-time)
Ownership
ใน Rust ทุกค่าจะมี “เจ้าของเดียว” และเมื่อเจ้าของนั้นสิ้นสุด scope หน่วยความจำที่จัดสรรไว้สำหรับค่าดังกล่าวจะถูกปล่อย (freed) ทันทีโดยอัตโนมัติ นี่คือหลักการของ Ownership
กฎพื้นฐานของ Ownership
- ในแต่ละเวลาจะมี “เจ้าของ” สำหรับค่าหนึ่ง ๆ ได้เพียงตัวเดียว
- เมื่อเจ้าของถูกย้าย (moved) ค่าเดิมจะไม่สามารถถูกใช้งานได้อีก
- เมื่อ scope ของเจ้าของหมด หน่วยความจำที่ใช้จะถูกปล่อยทันที
เช่นตัวอย่างนี้
s1
เป็นเจ้าของString
เมื่อs2 = s1;
Rust จะย้าย (move) ความเป็นเจ้าของไปที่s2
ทำให้s1
ไม่สามารถใช้งานได้อีก
Borrowing
เพื่อหลีกเลี่ยงการย้ายความเป็นเจ้าของ เราสามารถ “ยืม” ค่าโดยใช้การอ้างอิง (references) การยืมทำให้เราสามารถใช้ค่าหนึ่งได้โดยไม่ต้องย้าย ownership
กฎพื้นฐานของ Borrowing
- เราสามารถยืมค่าผ่าน reference ได้โดยใช้
&
- การยืมค่าจะมีสองแบบ: ยืมแบบอ่าน (
&T
) และยืมแบบแก้ไข (&mut T
) - ยืมแบบอ่านสามารถมีหลายครั้งพร้อมกันได้ แต่ “ยืมแบบแก้ไขต้องมีได้เพียงครั้งเดียว” ในเวลาเดียวกัน
Borrow แบบไม่สามารถแก้ไขได้
- ในตัวอย่างนี้ เราใช้
&s1
เพื่อยืมค่าs1
โดยไม่ย้าย ownership และยังสามารถใช้งานs1
ได้ภายหลัง
References
References
คือการยืมค่าผ่านการอ้างอิง โดยเราสามารถใช้เครื่องหมาย &
เพื่อสร้าง reference สำหรับค่าที่เราต้องการใช้ชั่วคราวโดยไม่ต้องย้ายความเป็นเจ้าของ
- Immutable References (
&T
): ยืมแบบอ่านได้อย่างเดียว เราไม่สามารถเปลี่ยนแปลงค่าของข้อมูลที่ถูกยืมได้ - Mutable References (
&mut T
): ยืมแบบที่สามารถเปลี่ยนแปลงค่าได้ แต่สามารถยืมแบบนี้ได้เพียงครั้งเดียวในเวลาเดียวกันเท่านั้น
ข้อจำกัดของ References
- เราไม่สามารถยืม mutable reference และ immutable reference พร้อมกันได้
สรุปทั้งหมด 3 concept
- Ownership: แต่ละค่าจะมีเจ้าของที่ชัดเจน เมื่อ scope ของเจ้าของสิ้นสุด ค่านั้นจะถูกปล่อยจากหน่วยความจำ
- Borrowing: การยืมค่าทำได้ผ่านการอ้างอิง โดยเราสามารถยืมได้ทั้งแบบ immutable และ mutable แต่ต้องปฏิบัติตามกฎของการยืม
- References: คือการยืมค่าชั่วคราวโดยไม่ต้องย้ายความเป็นเจ้าของ
Structs
https://doc.rust-lang.org/book/ch05-00-structs.html
Structs เป็นโครงสร้างข้อมูลที่ช่วยให้เรากำหนดประเภทข้อมูลที่ซับซ้อนมากกว่าการใช้ชนิดข้อมูลพื้นฐาน เช่น สามารถใช้เก็บหลาย ๆ ข้อมูลภายใต้โครงสร้างเดียวกัน คล้ายกับ “class” ในภาษาอื่น ๆ แต่ไม่มี method ภายในตัวโดยตรง (method จะถูกกำหนดแยกต่างหาก)
Rust มี Structs สามประเภท
- Classic Structs: โครงสร้างทั่วไปที่มีฟิลด์หลายแบบ
จาก code
- Struct
User
ถูกประกาศด้วยฟิลด์ 4 ฟิลด์:username
,email
,sign_in_count
, และactive
- สร้างตัวแปร
user1
ที่มีข้อมูลเหล่านี้ - เราสามารถเข้าถึงฟิลด์ใน Struct ผ่านเครื่องหมายจุด (
.
) เช่นuser1.username
- Tuple Structs: Struct แบบ Tuple ที่สามารถเข้าถึงฟิลด์ได้ผ่าน index
จาก code
Color
เป็น Tuple Struct ที่มีฟิลด์ 3 ฟิลด์คือi32
- การเข้าถึงฟิลด์ใช้เลข index เช่น
black.0
,black.1
, และblack.2
- Unit Structs: Struct ที่ไม่มีฟิลด์ใด ๆ
AlwaysEqual
เป็น Unit Struct ที่ไม่มีฟิลด์ สามารถใช้งานเมื่อเราต้องการสร้างประเภทข้อมูลใหม่ที่ไม่ต้องการเก็บข้อมูลใด ๆ
Enums
https://doc.rust-lang.org/book/ch06-00-enums.html
Enums เป็นประเภทข้อมูลที่สามารถเก็บค่าในหลายรูปแบบได้ภายใต้โครงสร้างเดียวกัน คล้ายกับ Union ในภาษาอื่น ๆ โดย Enums ช่วยให้เราเก็บค่าในรูปแบบที่แตกต่างกันแต่เป็นประเภทข้อมูลเดียวกันได้อย่างชัดเจนและปลอดภัย
ตัวอย่าง code
จาก code
- Enum
Message
ประกอบด้วย 4 รูปแบบ:Quit
,Move
,Write
, และChangeColor
Quit
ไม่มีข้อมูลใด ๆMove
มีฟิลด์{x, y}
แบบ objectWrite
เก็บค่าString
ChangeColor
เก็บข้อมูล 3 ค่าในรูปแบบi32
- ใช้
match
เพื่อจัดการกับค่าที่แตกต่างกันในMessage
Error Handling
https://doc.rust-lang.org/book/ch09-00-error-handling.html
Error Handling ใน Rust เป็นแนวคิดที่สำคัญในการจัดการกับสถานการณ์ที่ไม่คาดคิดหรือการทำงานผิดพลาดในโปรแกรม โดย Rust ใช้รูปแบบการจัดการข้อผิดพลาดที่ชัดเจนและปลอดภัยจากการทำงานผิดพลาดที่อาจเกิดขึ้นใน runtime ซึ่งจะทำให้โปรแกรมมีความเสถียรและสามารถจัดการกับสถานการณ์เหล่านั้นได้อย่างถูกต้อง
Rust ใช้โครงสร้างข้อมูลหลัก 2 ชนิดในการจัดการข้อผิดพลาด
Result<T, E>
: ใช้สำหรับข้อผิดพลาดที่สามารถคาดการณ์ได้panic!
: ใช้สำหรับข้อผิดพลาดที่ไม่สามารถกู้คืนได้
Result<T, E>
Result
เป็น enum ที่มีสองค่าหลัก
Ok(T)
สำหรับกรณีที่การทำงานสำเร็จErr(E)
สำหรับกรณีที่เกิดข้อผิดพลาด
รูปแบบของ Result<T, E>
ตัวอย่าง code
จาก code
- function
divide
จะคืนค่าResult
ซึ่งอาจเป็นOk(f64)
ในกรณีที่การหารสำเร็จ หรือErr(String)
หากมีการหารด้วยศูนย์ - ใน function
main
เราใช้match
เพื่อตรวจสอบค่าผลลัพธ์ว่าเป็นOk
หรือErr
และจัดการกับกรณีที่เกิดข้อผิดพลาด
วิธีการจัดการ Result
นอกจากใช้ match
แล้ว Rust ยังมีวิธีที่สะดวกในการจัดการกับ Result
เช่น:
unwrap
: ใช้เพื่อดึงค่าจากOk
ถ้าเป็นErr
จะทำให้โปรแกรมเกิด panic (เหมาะสำหรับกรณีที่มั่นใจว่าจะไม่มีข้อผิดพลาด)expect
: คล้ายกับunwrap
แต่เราสามารถกำหนดข้อความข้อผิดพลาดเองได้?
operator: ใช้เพื่อส่งต่อข้อผิดพลาดขึ้นไปยัง function ที่เรียกใช้งาน (เหมาะสำหรับการเขียน code แบบสั้นและเรียบง่าย)
- ตัวอย่าง
unwrap
- ตัวอย่าง
? operator
- เครื่องหมาย
?
ถูกใช้เพื่อตรวจสอบว่าค่าที่คืนมาจากstd::fs::read_to_string
เป็นOk
หรือErr
- ถ้าเป็น
Err
, function จะคืนข้อผิดพลาดไปยัง function ที่เรียกใช้งานโดยอัตโนมัติ
panic
panic!
เป็นวิธีการจัดการข้อผิดพลาดที่ไม่สามารถกู้คืนได้ เช่น เมื่อเกิดข้อผิดพลาดร้ายแรงที่ไม่สามารถดำเนินการต่อได้ โปรแกรมจะหยุดทำงานและทำการ “panic” โดยแสดงข้อความข้อผิดพลาดและ trace stack
ตัวอย่าง
- ในตัวอย่างนี้ เมื่อพยายามเข้าถึง index ที่เกินขอบเขตของเวกเตอร์ โปรแกรมจะเกิด panic และหยุดการทำงาน
Note: ควรหลีกเลี่ยงการใช้ panic!
ในกรณีที่เราสามารถจัดการกับข้อผิดพลาดได้ เพราะ Rust ต้องการให้โปรแกรมจัดการข้อผิดพลาดด้วยการใช้ Result
เพื่อให้โปรแกรมมีความทนทานต่อข้อผิดพลาดที่อาจเกิดขึ้นได้
บทสรุป Error Handling
Result<T, E>
: ใช้สำหรับจัดการข้อผิดพลาดที่สามารถคาดการณ์ได้ เช่น การอ่านไฟล์ การทำงานกับ I/O หรือการคำนวณที่อาจมีข้อผิดพลาดpanic!
: ใช้ในกรณีที่เกิดข้อผิดพลาดร้ายแรงที่ไม่สามารถกู้คืนได้ และควรใช้ในกรณีที่โปรแกรมไม่สามารถดำเนินการต่อได้
feature การจัดการข้อผิดพลาดของ Rust ถูกออกแบบมาเพื่อช่วยให้คุณเขียน code ที่มีความเสถียรมากขึ้น panic!
macro เป็นสัญญาณบอกว่าโปรแกรมของคุณอยู่ในสถานะที่ไม่สามารถจัดการได้ และช่วยให้คุณสั่งให้กระบวนการหยุดทำงานแทนที่จะพยายามดำเนินการต่อด้วยค่าที่ไม่ถูกต้องหรือผิดพลาด ส่วน Result
enum ใช้ระบบประเภทของ Rust เพื่อบ่งบอกว่าการดำเนินการบางอย่างอาจล้มเหลว ซึ่ง code ของคุณอาจกู้คืนได้ คุณสามารถใช้ Result
เพื่อบอก code ที่เรียก code ของคุณว่าจำเป็นต้องจัดการความเป็นไปได้ของความสำเร็จหรือความล้มเหลว การใช้ panic!
และ Result
ในสถานการณ์ที่เหมาะสมจะทำให้ code ของคุณมีความน่าเชื่อถือมากขึ้นเมื่อเกิดปัญหาที่หลีกเลี่ยงไม่ได้
Option
Option<T>
เป็นประเภทข้อมูลที่ใช้จัดการกับค่าที่อาจจะมีหรือไม่มี โดยมีความคล้ายคลึงกับ Result<T, E>
แต่ถูกออกแบบมาเพื่อใช้กับสถานการณ์ที่ไม่จำเป็นต้องจัดการกับข้อผิดพลาดอย่างเป็นทางการ เช่น ในกรณีที่เราต้องการแสดงให้เห็นว่าค่าหนึ่งอาจจะ “มีค่า” หรือ “ไม่มีค่า” (null-like) ซึ่งทำให้หลีกเลี่ยงปัญหาของการใช้งานค่า null ที่ไม่ปลอดภัยในภาษาอื่น ๆ
รูปแบบของ Option<T>
Some(T)
หมายถึงมีค่า T ที่ถูกต้องอยู่None
หมายถึงไม่มีค่าใด ๆ (คล้าย null)
ตัวอย่างการใช้งาน
จาก code
- function
find_square_root
จะคืนค่าOption<f64>
ถ้าคำนวณ square root ได้ก็จะคืนค่าSome(value)
หากเป็นเลขติดลบซึ่งไม่สามารถคำนวณ square root ได้ จะคืนค่าNone
- เราใช้
match
เพื่อจัดการกับกรณีทั้งSome
และNone
ความแตกต่างระหว่าง Option
กับ Result
Option<T>
: ใช้สำหรับค่าที่อาจจะมีหรือไม่มี ไม่ได้มีข้อผิดพลาดชัดเจน- เช่น การหา element ใน list ถ้าไม่เจอก็คืนค่า
None
- เช่น การหา element ใน list ถ้าไม่เจอก็คืนค่า
Result<T, E>
: ใช้สำหรับการจัดการกับข้อผิดพลาดที่สามารถคาดการณ์ได้ โดยจะมีข้อมูลเกี่ยวกับข้อผิดพลาดในกรณีที่การทำงานไม่สำเร็จ- เช่น การอ่านไฟล์ ถ้าไฟล์ไม่สามารถเปิดได้จะคืนค่า
Err(e)
- เช่น การอ่านไฟล์ ถ้าไฟล์ไม่สามารถเปิดได้จะคืนค่า
ตัวอย่างอื่นๆเพิ่มเติม
- ตัวอย่างการใช้
Option<T>
ในสถานการณ์ทั่วไป- เป็นตัวอย่าง การดึงค่าจากเวกเตอร์
v.get(1)
คืนค่าOption<&i32>
ถ้าดึงค่าได้จะเป็นSome
ถ้า index เกินขอบเขตจะเป็นNone
- การใช้
unwrap
กับOption<T>
- หากเรามั่นใจว่าค่าที่ได้จะไม่เป็น
None
เราสามารถใช้unwrap()
เพื่อดึงค่าออกมาได้โดยตรง - ข้อควรระวัง: ถ้าใช้
unwrap()
กับNone
โปรแกรมจะเกิด panic ทันที ดังนั้นควรใช้เฉพาะกรณีที่มั่นใจว่าจะมีค่าเสมอ
- หากเรามั่นใจว่าค่าที่ได้จะไม่เป็น
- การใช้
?
กับOption<T>
- เครื่องหมาย
?
ใช้ได้กับOption
เช่นเดียวกับResult
เพื่อช่วยส่งค่าNone
กลับออกไปเมื่อไม่พบค่า โดยไม่ต้องเขียนmatch
หรือif let
เอง
- เครื่องหมาย
สรุประหว่าง Result<T, E>
และ Option<T>
Option<T>
: ใช้เมื่อค่าที่เรากำลังจัดการอาจจะ “มี” หรือ “ไม่มี” แต่ไม่มีข้อผิดพลาดเฉพาะเจาะจงResult<T, E>
: ใช้เมื่อเราจำเป็นต้องจัดการข้อผิดพลาดที่อาจเกิดขึ้นจากการทำงานของโปรแกรม
สรุป
นี่คือหัวข้อแรกของ Rust ที่เราหยิบมาแชร์กับทุกคน ตอนนี้ทุกคนก็จะรู้จักและว่า ภาษา Rust คืออะไร run ยังไงบ้าง ให้ผลลัพธ์อย่างไรบ้าง รวมถึง main concept ของ Rust อย่าง Variable, Control Flow, Function, Ownership, Struct, Enum, Error Handling ซึ่งในหัวข้อนี้ เราจะทำให้ทุกคนเห็นภาพ look & feel ภาษา Rust กันก่อน
หัวข้อถัดไป เราจะเริ่มมาเจาะลึกในประเด็นที่ลึกซึ้งขึ้นของ Rust กัน ทั้งเรื่อง Pointer, Generic Type, OOP, Package Mangement ใน Rust เพื่อให้เกิดภาพที่สมบูรณ์ของภาษา Rust ก่อนที่เราจะเริ่มหยิบมาทำ application กันนะครับ
- มาเรียน Canvas ผ่าน Pong game กันมี Video
มารู้จักกับ HTML Canvas ว่ามันคืออะไร ใช้ทำอะไรบ้าง และมารู้จักพื้นฐานเบื้องต้นของการทำ Animation บน Browser ว่ามีหลักการเป็นประมาณไหน
- หาจุดซื้อขายในโลกของ Bot Trade เขาทำกันยังไง ?มี Video มี Github
เรื่องนี้สืบเรื่องจากที่ผมไปนั่งฟังเรื่องการทำ AI มา แล้วก็มีคนมาสอบถามผมประมาณว่า ทำ bot trade เนี้ยจริงๆเขาทำกันยังไง ผมก็คิดว่าก็ไม่น่ายากนะ
- Redux และ Reactมี Video มี Github
รู้จักกับ Redux state management ที่ช่วยทำให้ application จัดการ state ได้สะดวกสบายยิ่งขึ้นกัน
- มาเรียนรู้การทำ Frontend Testing ผ่าน React กันมี Video
แนะนำพื้นฐานการทำ Component Testing สำหรับการทำ Unit testing ฝั่ง Frontend