C là tuyệt nhất (2025)
(sqlite.org)- SQLite là một engine cơ sở dữ liệu gọn nhẹ được triển khai bằng ngôn ngữ C từ năm 2000 và không có kế hoạch viết lại bằng ngôn ngữ khác
- C được đánh giá là ngôn ngữ phù hợp nhất cho SQLite về hiệu năng, khả năng tương thích, tối thiểu phụ thuộc, và tính ổn định
- Các ngôn ngữ hướng đối tượng (C++, Java, v.v.) có ràng buộc lớn khi gọi giữa các ngôn ngữ, còn cách tiếp cận thủ tục đôi khi lại đơn giản và nhanh hơn
- Các “ngôn ngữ an toàn” như Rust hay Go vẫn chưa đủ trưởng thành và không phù hợp với chiến lược chất lượng của SQLite (ví dụ: kiểm thử 100% nhánh, khôi phục OOM)
- Khả năng chuyển sang Rust trong tương lai vẫn để ngỏ, nhưng cần đáp ứng nhiều điều kiện như độ trưởng thành, khả năng tương thích, hiệu năng, và hỗ trợ công cụ
1. Vì sao C là lựa chọn tốt nhất
- SQLite lần đầu được triển khai bằng C chuẩn ngay từ đầu vào ngày 29 tháng 5 năm 2000, và đến nay vẫn chưa có kế hoạch chuyển sang ngôn ngữ khác
- Các lý do khiến C phù hợp với việc triển khai SQLite là hiệu năng, khả năng tương thích, ít phụ thuộc, và tính ổn định
1.1 Hiệu năng
- SQLite là thư viện cấp thấp được sử dụng với cường độ cao, nên tốc độ là yếu tố bắt buộc
- Ví dụ, hiệu năng đã được chứng minh trong các tài liệu “Internal Versus External BLOBs” và “35% Faster Than The Filesystem”
- C được gọi là “ngôn ngữ assembly có tính di động”, vì vừa cho phép kiểm soát gần với phần cứng vừa giữ được tính khả chuyển giữa các nền tảng
- Các ngôn ngữ khác có thể tuyên bố “nhanh bằng C”, nhưng không có ngôn ngữ nào tuyên bố nhanh hơn C
1.2 Khả năng tương thích
- Gần như mọi hệ thống đều cung cấp khả năng gọi thư viện được viết bằng C
- Ví dụ, Android có thể gọi SQLite từ ứng dụng Java thông qua adapter
- Nếu SQLite được viết bằng Java, thì các ứng dụng iPhone dựa trên Objective-C hoặc Swift đã không thể sử dụng nó
1.3 Tối thiểu phụ thuộc
- Thư viện viết bằng C có phụ thuộc runtime rất ít
- Ở cấu hình tối thiểu, SQLite chỉ cần các hàm sau của thư viện C chuẩn
- memcmp(), memcpy(), memmove(), memset(), strcmp(), strlen(), strncmp()
- Ngay cả ở bản build đầy đủ, cũng chỉ dùng thêm malloc(), free() và mức vào/ra tệp cơ bản
- Trong khi đó, các ngôn ngữ hiện đại thường đòi hỏi runtime cỡ nhiều MB và hàng nghìn interface
1.4 Tính ổn định
- C là một ngôn ngữ lâu đời và ít thay đổi, cung cấp hành vi rõ ràng và có thể dự đoán được
- Khi phát triển một engine cơ sở dữ liệu nhỏ, nhanh và đáng tin cậy như SQLite, việc đặc tả ngôn ngữ không thay đổi thường xuyên là rất quan trọng
2. Vì sao không được viết bằng ngôn ngữ hướng đối tượng
-
Một số nhà phát triển cho rằng chỉ có thể triển khai hệ thống phức tạp bằng ngôn ngữ hướng đối tượng, nhưng SQLite không như vậy
-
Thư viện viết bằng C++ hoặc Java thường chỉ dùng được trong các ứng dụng viết bằng cùng ngôn ngữ đó
- Trong khi thư viện C có thể được gọi từ gần như mọi ngôn ngữ
-
Hướng đối tượng là một mẫu thiết kế, không phải bản thân ngôn ngữ, và vẫn có thể triển khai cấu trúc hướng đối tượng trong C
-
Hướng đối tượng không phải lúc nào cũng là lựa chọn tốt nhất; đôi khi mã thủ tục đơn giản hơn và có lợi hơn về bảo trì lẫn hiệu năng
-
Ở giai đoạn đầu phát triển SQLite (đầu những năm 2000), Java còn non trẻ, còn C++ thì gặp vấn đề tương thích nghiêm trọng giữa các compiler
- Khi đó C rõ ràng là lựa chọn tốt hơn, và đến nay lợi ích của việc viết lại vẫn gần như không có
3. Vì sao không được viết bằng “ngôn ngữ an toàn”
- Gần đây, các “ngôn ngữ an toàn” như Rust và Go thu hút nhiều chú ý, nhưng SQLite vẫn được duy trì bằng C
- Trong 10 năm đầu của SQLite, không tồn tại ngôn ngữ an toàn nào
- Có thể viết lại bằng Go hoặc Rust, nhưng sẽ có rủi ro phát sinh lỗi mới và suy giảm tốc độ
- Ngôn ngữ an toàn chèn thêm các nhánh (branch) như kiểm tra biên mảng
- Với mã đúng, các nhánh đó sẽ không bao giờ chạy, nên không thể kiểm thử 100% nhánh
- Hầu hết các ngôn ngữ an toàn sẽ dừng chương trình khi hết bộ nhớ (OOM)
- SQLite được thiết kế để khôi phục bình thường ngay cả khi OOM, nên điều này xung đột với cách vận hành đó
- Các ngôn ngữ an toàn hiện có đều mới và đang thay đổi nhanh
- SQLite ưu tiên những ngôn ngữ lâu đời và ổn định
4. Khả năng chuyển sang Rust
- SQLite có thể được viết lại bằng Rust, nhưng gần như không thể chuyển sang Go
- Lý do: Go không thích assert()
- Các điều kiện tiên quyết để chuyển sang Rust
- Rust phải trưởng thành hơn và chậm lại về tốc độ thay đổi để trở thành “một ngôn ngữ lâu đời và ổn định”
- Rust phải có khả năng tạo ra thư viện phổ dụng có thể được gọi từ mọi ngôn ngữ
- Rust phải tạo được object code chạy được cả trên thiết bị nhúng không có hệ điều hành
- Rust phải có hệ công cụ hỗ trợ kiểm thử độ bao phủ nhánh 100%
- Rust phải cung cấp cơ chế khôi phục OOM
- Rust phải chứng minh được việc triển khai không bị mất hiệu năng so với mức C
- Các nhà phát triển cho rằng Rust đã đáp ứng được các điều kiện trên có thể liên hệ trực tiếp với nhóm SQLite để thảo luận
5. Kết luận
- SQLite là một engine cơ sở dữ liệu nhỏ, nhanh và đáng tin cậy, và các đặc tính của ngôn ngữ C phù hợp với mục tiêu đó
- Việc chuyển sang ngôn ngữ hướng đối tượng hoặc ngôn ngữ an toàn không đem lại lợi ích thực tế về khả năng tương thích, hiệu năng hay quản lý chất lượng
- Sự đơn giản và ổn định của C là nền tảng cho khả năng bảo trì dài hạn và độ tin cậy của SQLite
8 bình luận
Dù sao thì đây cũng là dự án không nhận PR mà... họ cứ dùng thứ họ muốn dùng thôi
Nếu ở trong bối cảnh cần sự gọn nhẹ thì không có ngôn ngữ nào thay thế được C, C++ hay Rust. Chỉ là không có nhiều lập trình viên thực sự đồng cảm với việc phát triển mà phải lo ngại tràn số hoặc bị khai thác ở mức bit trên struct hoặc map.
Tiêu đề giật gân quá. Nếu đọc bài gốc thì sẽ thấy đây là bài viết nói về lý do vì sao C là ngôn ngữ phù hợp nhất để phát triển SQLite. Mong mọi người bớt giận.
Không, thậm chí chính bài viết này cũng được viết từ 7 năm trước rồi sao? Có vẻ như sau đó có bổ sung thêm nội dung nên được cập nhật một phần vào năm 2025... 🤦
Điều quan trọng là trong nhiều tình huống phát triển khác nhau, phải biết đưa ra phán đoán để dùng ngôn ngữ phù hợp; việc đặt một tiêu đề như vậy, như thể có một ngôn ngữ nào đó lúc nào cũng tốt hơn, thì trình độ tư duy đúng là chỉ ngang học hết cấp hai...
Ưu điểm lớn nhất của C, theo tôi, là nó chạm trực tiếp vào bản chất rằng “máy tính là một chuỗi bit”. Triết lý đơn giản của C và việc
reinterpret castmạnh tay tạo nên sức hút ở chỗ người dùng gần như luôn có thể biết nó sẽ được biên dịch thành loại mã máy nào. Không phải vì là C nên có thể được gọi từ mọi ngôn ngữ, mà là vì ABI mới là thứ có thể được gọi; còn trong C, đơn giản là có thể dự đoán được (hoặc phải dự đoán được) đầu vào và đầu ra là chuỗi bit như thế nào. Tôi cũng luôn nghĩ rằng khi bàn về tính khả thi của việc triển khai, điều quan trọng là phải phân biệt xem điều đó là bất khả thi trên máy Turing, hay chỉ là bất khả thi trong ngôn ngữ hoặc framework mà ta đang dùng hiện tại.Ý kiến trên Hacker News
Tôi không nghĩ cần phải biện minh vì sao không phải mọi dự án hay mọi lập trình viên đều dùng Rust hoặc Zig
Trên Hacker News và các nền tảng khác, có xu hướng quảng bá các ngôn ngữ này quá mức
Nếu đã đạt được kết quả đủ tốt với C và người dùng cũng hài lòng, thì người ngoài không có lý do gì để bàn ra tán vào
Những người quan tâm đến tiến bộ công nghệ khám phá tiềm năng của ngôn ngữ mới là một diễn biến tự nhiên
Tuy vậy, người ngoài có quyền nêu ý kiến, nhưng dự án không có nghĩa vụ phải nghe
Rust làm giảm mức độ lộ ra của lỗi, nhưng những loại lỗi tương tự vẫn tồn tại
Lập trình viên C có xu hướng phản ứng nhạy hơn với race condition, còn Rust có nguy cơ khiến người ta quá tin vào các chú thích “an toàn”
Ngoài ra trong Rust, việc sửa đổi không hề đơn giản nên gánh nặng refactor có thể lớn hơn
Cuối cùng, Rust là một ngôn ngữ thú vị nhưng không phải vạn năng, và không nên bị áp đặt
Những ngôn ngữ như Rust hay Zig có chủ đích rời xa các pattern hướng đối tượng truyền thống
OOP từng hấp dẫn như một khung khái niệm mang lại “giác ngộ”, nhưng trên thực tế thường làm tăng độ phức tạp và gây hại cho tính mô-đun
Giống như việc dùng máy khoan điện thay vì khoan tay là điều hiển nhiên, nếu có công cụ tốt hơn thì dùng nó là tự nhiên
Nhưng các công cụ và hoạt động đào tạo để viết mã C an toàn cũng cần phát triển đủ mạnh
Tôi là một Rustacean khá nghiêm túc, nhưng không nghĩ việc viết lại mọi dự án bằng Rust là hợp lý
Nếu chuyển một dự án C đã được kiểm chứng kỹ sang Rust thì trong ngắn hạn thậm chí khả năng bug tăng lên còn cao
Tuy vậy, một số nơi đang thử thách mình với việc viết lại bằng Rust, ví dụ như Limbo: dự án viết lại hoàn toàn SQLite bằng Rust
Một trong những ưu điểm chính của SQLite là có thể được nhiều tiến trình truy cập đồng thời, nên nếu thiếu điều này thì phạm vi ứng dụng sẽ hẹp đi
Cứ tự tạo một phiên bản mới và thử nghiệm xem có thành công hay không là được
Tôi từng có kinh nghiệm migrate RediSearch sang Rust
Lý do là vì gần đây có nhiều lỗ hổng CVE
Nếu SQLite không có vấn đề như vậy thì lý do để chuyển sang Rust là khá yếu
Có lẽ phải cần đến vài chục năm nữa mới hiểu được đầy đủ điểm mạnh và giới hạn của Rust
Đặc biệt, hiệu quả của Rust trong ứng dụng GUI vẫn còn chưa rõ ràng
Rust có lẽ phải đến khoảng năm 2040 mới đạt được mức độ đáng tin cậy tương tự
Như Linus đã nhắc đến, Rust cần có cơ chế phục hồi OOM (Out of Memory)
Có thể xem thêm tại liên kết thảo luận LKML
Trong mã embedded hoặc mã kernel, thậm chí có thể tắt hoàn toàn tính năng cấp phát
Tức là Rust vốn đã cung cấp toàn quyền kiểm soát bộ nhớ
Khẳng định “không có ngôn ngữ đa dụng nào nhanh hơn C” là một so sánh phiến diện vì bỏ qua thời gian của lập trình viên
Thay vì mất 5 giờ để làm ra một chương trình chạy 4 giây bằng C, trong thực tế có thể hợp lý hơn nếu dùng ngôn ngữ khác để làm trong 5 phút ra chương trình chạy 5 giây
Càng nhiều người dùng, khác biệt tốc độ dù rất nhỏ cũng càng tích lũy thành giá trị lớn
Rust còn lâu mới trở thành một ngôn ngữ “nhàm chán nhưng ổn định”
C được quản lý bởi một ủy ban bảo thủ và giữ tương thích rất nghiêm ngặt, còn Rust thì để giải quyết vấn đề mà ưu tiên đổi mới hơn tương thích
Mã từ các phiên bản cũ vẫn có thể tiếp tục build bằng compiler mới
Tôi nghĩ cách này tốt hơn kiểu cứ phải tiếp tục mang vác các tính năng cũ như C++
Ngược lại, những ngôn ngữ do ủy ban thiết kế như C++ hay Common Lisp đã trở nên phức tạp hơn
Rust cũng có quy mô lớn nên cần thận trọng khi dùng cho hệ thống embedded hoặc hệ thống cốt lõi
Tôi đồng cảm với thái độ “đừng cố sửa thứ vốn đang chạy tốt”
Lịch sử phát triển ngôn ngữ có vẻ như là quá trình lặp đi lặp lại giữa việc giải quyết vấn đề và tạo ra độ phức tạp mới
C là ví dụ tiêu biểu cho triết lý “Worse is Better”, và đã thành công suốt hàng chục năm nhờ sự đơn giản
Ngược lại, Rust theo đuổi “Right Thing™”
Ngày nay, gánh nặng phải tự triển khai mọi thứ đã giảm đi trong phần lớn môi trường, nên giờ đây nó có thể là lựa chọn tốt hơn
Nhưng không cần phải cố chuyển những dự án đã thành công sang đó
Nếu là dự án mới thì rất có thể Rust là lựa chọn tốt hơn
Sự đơn giản và ổn định của C là ưu điểm đang bị đánh giá thấp
Tôi nghĩ thay vì tiếp tục thay đổi chính ngôn ngữ, sẽ tốt hơn nếu trau chuốt thư viện chuẩn hoặc hệ sinh thái
Không chỉ sự đơn giản mà bảo đảm hành vi mang tính xác định cũng rất quan trọng
Ví dụ tôi thích các tính năng như designated initializer, compound literal, alignas, memset_explicit
Cá nhân tôi vẫn cho rằng C là tốt nhất
Rust có nhiều ý tưởng hay, nhưng cũng là một ngôn ngữ có nhược điểm rất rõ
Hiện vẫn còn bầu không khí khiến việc thảo luận lạnh lùng về những vấn đề của Rust trở nên khó khăn
Ví dụ có thể là độ dốc học tập hay độ phức tạp của đóng gói
Câu nói “mọi hệ thống đều có thể gọi thư viện C” giờ không còn đúng nữa
Rust và Zig cũng đáp ứng được yêu cầu này
Tuy nhiên, thư viện chuẩn của Rust thường panic thay vì phục hồi khi OOM, và tài liệu cũng còn thiếu
extern "C"hoặcexportthì mới tương thích ABI của CNếu không thì ABI không được định nghĩa, nên ngược lại còn có thể kém ổn định hơn cả C++
Vấn đề này có thể càng lớn hơn, đặc biệt trong các bản phân phối Linux phát hành Rust crate