เรียนรู้ C3
(alloc.dev)- C3 มีพื้นฐานมาจากภาษา C และมี ความสามารถขั้นสูง เช่น โมดูล, operator overloading, generics และการรันตอนคอมไพล์
- โดยยังคงไวยากรณ์ C ที่คุ้นเคยไว้ พร้อมเสริม ไวยากรณ์ที่เพิ่มประสิทธิภาพและความเสถียร เช่น การจัดการข้อผิดพลาด, defer, foreach
- เพิ่ม สัญญาเชิงประกาศ (contracts) และ optional type กับรูปแบบการจัดการข้อผิดพลาด เพื่อยกระดับ ความปลอดภัยและความชัดเจน
- รองรับ สภาพแวดล้อมการพัฒนาที่ใช้งานได้จริง เช่น standard library และ การรวม build system, การจัดสรรหน่วยความจำชั่วคราว
- มีความคล้ายกับ ภาษา Zig ในด้านการ build, การสร้างโปรเจกต์, โครงสร้างโค้ด และสะท้อนให้เห็นการทดลองด้าน การออกแบบภาษา แบบใหม่
ภาพรวมและจุดเด่นของ C3
C3 คืออะไร?
- C3 เป็นภาษาที่สร้างต่อยอดบนภาษา C โดยยังคง ไวยากรณ์ที่คุ้นเคย ไว้ ขณะเดียวกันก็เพิ่มความสามารถที่ C ทำได้ยาก เช่น ระบบโมดูล, operator overloading, generics, การรันตอนคอมไพล์, การจัดการข้อผิดพลาด, defer, value methods, contracts แบบค่อยเป็นค่อยไป, slices, foreach, การรองรับ dynamic type
- ใช้โครงสร้างโมดูลที่อาศัย namespace เพื่อป้องกัน การชนกันของชื่อ (
abc::Contextเป็นตัวอย่างของ imperative namespace) - เป้าหมายหลักคือ เพิ่มผลิตภาพ และมอบ ความสามารถสมัยใหม่สำหรับ system programming อย่างปลอดภัย
คุณลักษณะของภาษา
ตัวอย่าง Hello World
- มีความคล้ายกับ C ในเชิงไวยากรณ์
- ต้องระบุคีย์เวิร์ด
fnอย่างชัดเจนในการประกาศฟังก์ชัน - ฟังก์ชันใน standard library สำหรับ I/O และงานอื่น ๆ มีความสามารถสูง และสามารถพิมพ์ค่าหลายชนิดได้ทันที
ลูป foreach
- ต่างจาก C ตรงที่รองรับ ไวยากรณ์ foreach มาให้โดยตรง
- การวนซ้ำผ่าน reference ต้องใส่
&หน้าชื่อตัวแปร (ความสามารถขั้นสูง) - รองรับ break และ continue และมีลักษณะคล้าย foreach ของภาษาอื่น
ลูป while
- ก่อน C99 ไม่สามารถประกาศตัวแปรภายในเงื่อนไขของ while ได้ แต่ใน C3 สามารถประกาศภายในได้
enum และคำสั่ง switch
- รองรับ break แบบ implicit ในคำสั่ง switch (การปะปนระหว่าง implicit/explicit break อาจมีทั้งคนชอบและไม่ชอบ)
- รองรับ การย้ายเคสอย่างชัดเจน ผ่านคีย์เวิร์ด nextcase (ทำให้ทำ jump table ได้ง่าย)
- ควบคุม flow ของ switch-case ได้กระชับกว่าแนวทางที่เคยซับซ้อนในภาษาอย่าง Zig หรือ C
คีย์เวิร์ด defer
- เมื่อ scope สิ้นสุด จะ รันคำสั่งที่จองด้วย defer ย้อนลำดับ เพื่อรับประกันการคืนทรัพยากรอย่างปลอดภัย
- สามารถใช้ defer ร่วมกับ
catch,tryได้ด้วย (ควบคุม flow ของการจัดการข้อผิดพลาด)
struct และ union
- อนุญาตให้มี sub-struct/sub-union แบบมีชื่อหรือ anonymous ภายใน struct ทำให้ออกแบบแพตเทิร์น tagged union ได้ง่าย
- แยกความต่างระหว่าง anonymous (ฟิลด์ชื่อซ้ำกัน) กับการชนกันของชื่ออย่างเข้มงวดและชัดเจน
แนวทางการจัดการข้อผิดพลาด
- รองรับ optional type ผ่านสัญลักษณ์
?และรวม error กับ value option เข้าไว้ด้วยกันเพื่อความสะดวก - ใช้คีย์เวิร์ด catch เพื่อแตกแขนงเมื่ออยู่ในสถานะว่าง (ไม่มี Optional) หรือเกิด error
- ต่างจาก Rust และ Zig ตรงที่ แยก error กับ optional value ได้ไม่ชัดนัก (ข้อดี: เรียบง่าย, ข้อเสีย: เจตนาชัดเจนน้อยลง)
- สามารถส่งต่อ exception ได้ด้วยตัวดำเนินการ
!(rethrow)
Contracts
- เขียนเงื่อนไขก่อน/หลังฟังก์ชัน (Require/Ensure) ภายใน
<* .. *>(ตรวจเงื่อนไขตอนคอมไพล์) - รองรับการวิเคราะห์ fold ใน compile time ด้วย (แต่ยังไม่ทำ static analysis)
เมธอดของ struct
- สร้าง associated methods ด้วยการระบุชนิด + dot notation (
Foo.next) และมี namespace (รวมถึง primitive) - อนุญาตให้มีเมธอดกับทุกชนิด เช่น struct/union/enum
แมโคร
- แมโครที่อิงการประเมินค่าในช่วงคอมไพล์ (
macrokeyword) - ใช้
$สำหรับพารามิเตอร์ตอนคอมไพล์ และ#สำหรับส่งผ่านก่อนประเมินค่า - เป็นสไตล์แบบ C (ลดปัญหาแมโครพันกัน เน้นความเสถียรของ AST มีการตรวจด้วยคำนำหน้า @ เป็นต้น)
- ใช้แมโครเพื่อทำ type reflection และการรันตอนคอมไพล์
คุณสมบัติของชนิดข้อมูล
alignof, kindof, extnameof, sizeof, typeid, methodsof, has_tagof, tagof, is_eq, is_ordered, is_substructเป็นต้น- เหมาะกับ metaprogramming และ reflection
Base64/Hex literals
- สามารถประกาศ ลำดับไบต์โดยตรง ได้ในรูปแบบ
b64"..." x"..." - มีแมโครในตัว
$embedเป็นทางเลือกทดแทน (ในทางปฏิบัติใช้ไม่บ่อย)
Primitive types
- มีชนิดพื้นฐานหลากหลาย เช่น int, uint, char (เป็น unsigned เสมอ), bool, float, int128/uint128
- มีชนิดเฉพาะสำหรับ pointer/size เช่น iptr, uptr, isz, usz (อาจไม่ค่อยตรงไปตรงมานัก)
- ต่างจาก C ตรงที่รับประกัน bit-size
อื่น ๆ
- มาพร้อมชุดความสามารถที่กว้างขวาง เช่น operator overloading, struct subtyping, generics, runtime dispatch, any type, bitstructs
ภาคปฏิบัติ: ประสบการณ์ใช้งาน C3
การติดตั้ง C3
- รองรับทั้งไบนารีที่ build ไว้ล่วงหน้าจากเว็บไซต์ทางการ และการ build จากซอร์สโดยตรง
- ต้องติดตั้ง LLVM และ LLD (หากมีปัญหาเรื่องการลิงก์ ให้ใช้ CMake flags
-DLLVM_DIR,-DLLD_DIR) - บางดิสโทรมีปัญหาไม่รวมไลบรารี LLD มาด้วย จึงแนะนำให้ดาวน์โหลดไบนารีโดยตรง
- คอมไพเลอร์ C3 ต้องพึ่งพา libtinfo
การสร้างโปรเจกต์
- ใช้คำสั่ง
c3c initเพื่อสร้างโครงสร้างโฟลเดอร์มาตรฐาน (LICENSE/README.md/project.json/srcเป็นต้น) - ตั้งค่าโครงโปรเจกต์พื้นฐาน เช่น Bluild, build target, การตั้งค่าซอร์ส (คล้าย Zig และ Cargo)
- ไฟล์ main.c3 เริ่มต้นกระชับมาก (ความเห็น: เหมาะกับผู้ใช้ใหม่)
การสร้างเครื่องคิดเลข
การออกแบบและเป้าหมาย
- ใช้ C3 ฝึกไวยากรณ์หลากหลายด้าน เช่น ฟังก์ชัน, I/O, การจัดการหน่วยความจำ, ลูป ผ่านการทำ recursive descent parser และลอจิกหลักของเครื่องคิดเลข
- เป้าหมายคือดูข้อดีข้อไม่สะดวกด้วยตนเอง ทั้งเรื่องความตรงไปตรงมาของไวยากรณ์และผลิตภาพในการใช้งานจริง
การจัดการอินพุต
- ใช้ temporary allocator (tmem) ผ่าน @pool โดยคืนหน่วยความจำอัตโนมัติเมื่อ scope สิ้นสุด (arena allocator)
- รองรับทั้ง tmem (ชั่วคราว) และ mem (ทั่วไป) ซึ่งเป็นรูปแบบการจัดการหน่วยความจำมาตรฐาน พร้อมแพตเทิร์นการส่ง allocator ระดับฟังก์ชัน (ผสมข้อดีของ Zig กับ C)
- ฟังก์ชัน main ต้องระบุค่าที่คืนอย่างชัดเจนเสมอ (คอมไพเลอร์บังคับ)
- ฟังก์ชันที่ไม่เป็นไรหากละเลยค่าที่คืน ต้องติดแอตทริบิวต์ @maydiscard (ป้องกันการเมินผลลัพธ์โดยไม่ตั้งใจ)
การทำ tokenizer
- แยกอินพุตของผู้ใช้ออกเป็นรายการโทเคน
- ใช้โครงสร้างควบคุมหลายแบบ เช่น List ใน standard library, ไวยากรณ์ foreach, switch-case (nextcase, การผสม implicit/explicit break)
- มีความสับสนอยู่บ้างกับไวยากรณ์ slice (รวมดัชนีปลายทั้งสองด้าน) และ slice ความยาว 0 (มีไวยากรณ์ระบุความยาวแยกต่างหาก)
- เมื่อเทียบกับภาษาอย่าง Rust แล้ว การผสมใช้ temporary/general allocator แสดงให้เห็นถึง ความโปร่งใสและความยืดหยุ่นของการจัดการหน่วยความจำ ที่ดีกว่า
การทำ parser
- ประสบการณ์เขียน parser โดยตรง (ละไว้)
บทสรุปและความเห็นโดยรวม
- C3 มุ่งหาจุดร่วมระหว่าง ภาษาระบบแบบดั้งเดิม กับ การออกแบบสมัยใหม่
- ศึกษาแนวทางของ Zig, Rust, C และออกแบบให้เป็นภาษาที่รักษาสมดุลระหว่าง ประสิทธิภาพกับความเสถียรของโค้ด
- มีจุดเด่นด้าน modularity, การจัดการหน่วยความจำ/ข้อผิดพลาด/contracts อย่างปลอดภัย, metaprogramming ที่ทรงพลัง, build system ที่ตรงไปตรงมา
- เส้นโค้งการเรียนรู้ สำหรับผู้มีประสบการณ์ C ถือว่าเริ่มต้นได้อย่างค่อยเป็นค่อยไป
- ยังต้องปรับปรุงเรื่อง ecosystem ที่ยังไม่โตเต็มที่ เช่น language server, IDE และบางไวยากรณ์ที่อาจมีทั้งคนชอบและไม่ชอบ
- น่าจับตาในฐานะ ภาษาทางเลือกยุคถัดไป สำหรับงาน low-level/system development
ยังไม่มีความคิดเห็น