Vì sao chọn Zig thay C cho lập trình hệ thống
Một lập trình viên hệ thống giải thích lý do chọn Zig cho code mới thay vì C: dọn dẹp tài nguyên gọn hơn với errdefer, slice tự biết độ dài, kiểm tra tràn số nguyên, và zig cc dùng làm trình biên dịch C đa nền tảng. Bài viết cũng nêu rõ giới hạn: Zig chưa ổn định như C.

Một bài viết trên blog cá nhân của M. Tsoukalos lý giải vì sao sau khi so sánh Zig với Rust, tác giả tiếp tục đặt câu hỏi tương tự với C — và đi đến kết luận chọn Zig cho mọi đoạn code mới. Khác với Rust, câu hỏi ở đây không phải về an toàn bộ nhớ mà về những điểm khiến C tốn thời gian.
Điểm mấu chốt: Zig và C theo đuổi cùng mục tiêu — ngôn ngữ nhỏ, không có bộ thu gom rác (Garbage Collector), bám sát phần cứng. Toàn bộ code trong bài được biên dịch trên máy Mac với Zig 0.17 và clang.
Những gì C làm đúng
Tác giả nhấn mạnh C là ngôn ngữ của UNIX: nhân hệ điều hành, thư viện C và hầu hết thư viện hệ thống mà mọi ngôn ngữ khác gọi đến cuối cùng đều viết bằng C, còn các trang man mô tả chúng bằng nguyên mẫu và ví dụ C. Một chương trình C từ thập niên 1990 vẫn biên dịch được hôm nay, và gần như mọi CPU từng được sản xuất đều có trình biên dịch C.
Ngôn ngữ này nhỏ đến mức chỉ một cuốn sách, The C Programming Language của Kernighan và Ritchie, là đủ bao quát. Zig không cố thay thế những thứ đó: nó gọi trực tiếp thư viện C và dùng C ABI khi cần.
"Tôi không viết bài này vì nghĩ C tệ; C là ngôn ngữ lập trình tôi thích nhất! Tôi viết vì không muốn viết code mới bằng C nữa."
Dọn dẹp tài nguyên không cần goto
Đoạn code C mà tác giả ít thích nhất là hàm cấp phát nhiều tài nguyên và phải giải phóng theo thứ tự ngược lại khi lỗi giữa chừng. Cách quen thuộc, cũng là cách nhân Linux dùng, là nhảy bằng goto kèm các nhãn fail_name, fail_user, fail. Cách này chạy được, nhưng phần dọn dẹp nằm xa chỗ cấp phát và thứ tự nhãn phải khớp thủ công với thứ tự cấp phát.
Trong Zig, mỗi lệnh errdefer nằm ngay dưới lệnh cấp phát mà nó hoàn tác, và chỉ chạy khi hàm trả về lỗi. Zig tự chạy chúng theo thứ tự ngược lại, nên không còn nhãn nào phải xếp thứ tự.
Điểm tác giả thích hơn nữa là có thể kiểm thử mọi nhánh lỗi. Kiểu std.testing.FailingAllocator sẽ làm hỏng lần cấp phát thứ nhất, thứ hai hoặc thứ ba tùy giá trị fail_index; còn allocator kiểm thử bên dưới báo lại mọi vùng nhớ không được giải phóng. Với C, muốn thử ba nhánh này phải bọc malloc() hoặc dùng công cụ ngoài.
Slice tự biết độ dài
Trong C, mảng truyền vào hàm trở thành con trỏ và độ dài phải truyền riêng, nếu có. Tác giả dẫn một hàm C có lỗi lệch một đơn vị (off-by-one), một macro tính hai lần một trong các đối số, và các dự án C lớn thường đầy dòng #ifdef cho từng hệ điều hành.
Với Zig, code chạy lúc biên dịch cũng là code Zig bình thường. Thay vì #ifdef __linux__, lập trình viên viết if (builtin.os.tag == .linux) và trình biên dịch loại bỏ nhánh không áp dụng. Cấu trúc dữ liệu tổng quát chỉ là hàm nhận một kiểu và trả về một kiểu, và trình biên dịch kiểm tra tất cả như phần còn lại của chương trình.
Ít bất ngờ từ hành vi không xác định
Trong C, tràn số nguyên có dấu là hành vi không xác định (undefined behavior). Chương trình cộng 1 vào INT_MAX khi biên dịch bằng clang in ra -2147483648 và thoát với mã 0, nhưng chuẩn C cho phép trình biên dịch làm bất cứ điều gì, kể cả xoá đoạn code mà nó cho là không bao giờ chạy.
Trong Zig, tràn số nguyên — cả có dấu lẫn không dấu — được kiểm tra ở chế độ debug và chế độ an toàn; chương trình dừng với thông báo panic: integer overflow kèm số dòng xảy ra. Khi muốn phép cộng quay vòng, lập trình viên viết +%; khi muốn kết quả dừng ở giá trị lớn nhất, viết +|.
Zig cũng là trình biên dịch C
Lệnh zig cc là một trình biên dịch C dùng thay cho gcc hay clang, và biên dịch chéo (cross-compile) hoạt động ngay không cần cấu hình thêm. Trên máy Mac, lệnh zig cc -target x86_64-linux-musl -o sum sum.c tạo ra file thực thi Linux tĩnh mà không phải cài gì khác.
Ở chế độ debug, zig cc còn bật trình khử lỗi hành vi không xác định, nên chương trình cộng INT_MAX ở trên sẽ dừng với thông báo panic: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'. File build.zig có thể biên dịch cả file C lẫn file Zig, nhờ đó có thể chuyển một dự án C sang Zig từng file một mà vẫn giữ nguyên hệ thống build.
Với phần header C, Zig 0.17 đã bỏ @cImport(); người dùng chuyển sang zig translate-c.
Đánh đổi thẳng thắn
Zig không phải C đã sửa hết mọi vấn đề. Nó không ngăn bạn dùng vùng nhớ sau khi đã giải phóng, hay giữ con trỏ tới biến trên stack không còn tồn tại. Ở chế độ fast và small, phần lớn kiểm tra an toàn bị tắt và bạn quay lại vùng hành vi không xác định như C.
C còn ổn định theo cách Zig chưa làm được. Khi Zig 0.17 ra mắt, tác giả phải sửa 35 trong số 152 file Zig của cuốn sách mình, trong khi code C từ nhiều thập kỷ trước vẫn biên dịch bình thường. C có nhiều công cụ hơn, nhiều sách hơn, nhiều người biết hơn và có trình biên dịch cho những con chip mà Zig chưa hỗ trợ. Nếu viết code cho vi điều khiển chỉ có trình biên dịch C của nhà sản xuất, lựa chọn đã được định sẵn.
Kết luận
C sẽ không biến mất, và tác giả vẫn đọc rất nhiều C vì mã nguồn UNIX cùng phần lớn thư viện hệ thống viết bằng C. Nhưng với code mới, ông chọn Zig: cùng kiểu code bám sát phần cứng, không runtime, nhưng trình biên dịch và chế độ debug bắt được nhiều lỗi mà C lặng lẽ cho qua.
Góc nhìn
Với lập trình viên Việt Nam đang làm việc với C — từ firmware nhúng, driver, đến các thư viện hệ thống cấp thấp — điểm đáng chú ý nhất là zig cc. Công cụ này cho phép dùng Zig như một trình biên dịch C thay thế, kèm biên dịch chéo và trình khử lỗi hành vi không xác định, mà không phải viết lại toàn bộ dự án. Cách chuyển đổi dần từng file nhờ build.zig giúp giảm rủi ro so với việc đổi ngôn ngữ cùng lúc.
Tuy nhiên, chuyện Zig 0.17 làm hỏng hàng loạt file code cũ là lời nhắc rằng ngôn ngữ này chưa đạt độ ổn định của C. Với các dự án cần duy trì lâu dài hoặc phải chạy trên phần cứng chỉ có trình biên dịch của nhà sản xuất, C vẫn là lựa chọn an toàn hơn. Với code mới, đặc biệt là công cụ dòng lệnh hay dịch vụ hệ thống chạy trên nền tảng phổ biến, việc thử Zig ở phạm vi nhỏ là bước hợp lý để đánh giá trước khi cam kết.
Nguồn: mtsoukalos.eu · Quy trình biên tập
Bài viết liên quan

Công nghệ
Từ Rust sang Zig: Trải nghiệm thực tế của một lập trình viên hệ thống
19 tháng 9, 2026

Công nghệ
Tại sao các lập trình viên Functional nên dành một cái nhìn cho Zig
30 tháng 4, 2026

Phần mềm
Cha đẻ ngôn ngữ Zig theo đuổi "sự hoàn hảo không thỏa hiệp" trước khi ra mắt phiên bản 1.0
28 tháng 5, 2026