- Tập trung vào sự khác biệt về triết lý và hệ giá trị của ba ngôn ngữ, bài viết so sánh mỗi ngôn ngữ đang cố giải quyết vấn đề gì
- Go được mô tả là ngôn ngữ đề cao sự đơn giản và ổn định, tối giản tính năng để giúp cộng tác và bảo trì dễ dàng hơn
- Rust theo đuổi đồng thời tính an toàn và hiệu năng, bảo đảm an toàn bộ nhớ bằng hệ thống kiểu phức tạp và cấu trúc trait
- Zig được mô tả là ngôn ngữ thử nghiệm trao cho lập trình viên toàn quyền kiểm soát thông qua quản lý bộ nhớ thủ công và thiết kế hướng dữ liệu
- Cách tiếp cận đối lập của ba ngôn ngữ cho thấy hệ giá trị mà ngôn ngữ lập trình hiện thực hóa, và tiêu chí lựa chọn nằm ở việc nhà phát triển đồng cảm với triết lý nào
Góc nhìn để so sánh ngôn ngữ
- Tác giả muốn hiểu hệ giá trị của từng ngôn ngữ thông qua việc thử nghiệm các ngôn ngữ mới, chứ không phải những ngôn ngữ đang dùng ở nơi làm việc
- Tác giả nhấn mạnh rằng điều quan trọng không phải là chỉ so sánh danh sách tính năng, mà là ngôn ngữ đã chọn những đánh đổi nào
- Go, Rust và Zig có nhiều phần giao thoa về mặt chức năng, nhưng những giá trị mà nhà thiết kế coi trọng lại khác nhau
- Nắm được triết lý của từng ngôn ngữ giúp đánh giá chúng phù hợp với môi trường và mục đích nào
Go — ngôn ngữ của sự đơn giản và cộng tác
- Go được phân biệt bởi chủ nghĩa tối giản, với đặc điểm “có thể chứa toàn bộ ngôn ngữ trong đầu”
- Generics chỉ được thêm vào sau 12 năm, và các tính năng như tagged union hay cú pháp đường tắt cho xử lý lỗi vẫn chưa có
- Go rất thận trọng khi thêm tính năng, nên có nhiều mã boilerplate, nhưng đổi lại độ ổn định và khả năng đọc của ngôn ngữ cao
- Slice của Go bao quát chức năng của
Vec<T> trong Rust hay ArrayList trong Zig, và runtime tự động quản lý vị trí bộ nhớ
- Bắt nguồn từ sự bất mãn với độ phức tạp và độ trễ biên dịch của C++, ngôn ngữ này được thiết kế với mục tiêu đơn giản và biên dịch nhanh
- Go đề cao hiệu quả cộng tác trong môi trường doanh nghiệp, ưu tiên mã rõ ràng và tính nhất quán hơn các tính năng phức tạp
Rust — phức tạp nhưng mạnh mẽ về an toàn và hiệu năng
- Rust đề cao “zero-cost abstraction” và là một ngôn ngữ theo hướng tối đa hóa tính năng với nhiều khái niệm kết hợp
- Lý do Rust khó học là vì mật độ khái niệm cao, với hệ thống kiểu phức tạp và cấu trúc trait hiện diện khắp nơi
- Mục tiêu cốt lõi của Rust là kết hợp hiệu năng với an toàn bộ nhớ
- Để ngăn UB(Undefined Behavior), ngôn ngữ thực hiện kiểm chứng ở thời điểm biên dịch
- Điều này chặn hành vi không thể dự đoán do tham chiếu con trỏ sai hoặc giải phóng kép gây ra
- Để compiler có thể hiểu hành vi runtime của mã, lập trình viên phải định nghĩa tường minh kiểu và trait
- Nhờ cấu trúc đó, độ tin cậy đối với mã của người khác cao hơn và hệ sinh thái thư viện được duy trì sôi động
Zig — toàn quyền kiểm soát và thiết kế hướng dữ liệu
- Zig là ngôn ngữ mới nhất trong ba ngôn ngữ, hiện ở giai đoạn phiên bản 0.14 và hầu như chưa có tài liệu hóa đầy đủ cho thư viện chuẩn
- Ngôn ngữ này áp dụng quản lý bộ nhớ thủ công, nên lập trình viên phải tự gọi
alloc() và chọn allocator
- Khác với Rust hay Go, Zig cho phép tạo biến toàn cục một cách đơn giản và phát hiện “illegal behavior” ở runtime để dừng chương trình
- Có thể điều chỉnh cân bằng giữa hiệu năng và độ an toàn bằng 4 chế độ phát hành có thể chọn khi build
- Zig cố ý loại bỏ các tính năng của lập trình hướng đối tượng (OOP)
- Không có trường private hay dynamic dispatch, và ngay cả
std.mem.Allocator cũng không được triển khai như một interface
- Thay vào đó, ngôn ngữ hướng đến thiết kế hướng dữ liệu (data-oriented design)
- Về quản lý bộ nhớ, Zig cũng khuyến nghị cấu trúc cấp phát và giải phóng những khối bộ nhớ lớn theo chu kỳ, thay vì quản lý tinh vi theo từng đối tượng kiểu RAII
- Zig được mô tả là ngôn ngữ có khuynh hướng tự do và phản hệ thống, loại bỏ tư duy OOP và tối đa hóa quyền kiểm soát do lập trình viên nắm giữ
- Hiện tại đội ngũ đang tập trung vào viết lại toàn bộ các dependency, và phiên bản ổn định (1.0) vẫn chưa được xác định
Kết luận — sự khác biệt về giá trị mà ngôn ngữ bộc lộ
- Go lấy cộng tác và sự đơn giản, Rust lấy an toàn và hiệu năng, còn Zig lấy tự do và quyền kiểm soát làm giá trị cốt lõi
- Sự khác biệt giữa ba ngôn ngữ không chỉ là so sánh tính năng đơn thuần, mà phản ánh lựa chọn mang tính triết học về phát triển phần mềm
- Nhà phát triển sẽ chọn ngôn ngữ tùy theo mình đồng cảm với giá trị nào
1 bình luận
Ý kiến Hacker News
Trong Rust, việc tạo biến toàn cục có thể thay đổi không hề khó
Chỉ là phải dùng
unsafehoặc smart pointer có cung cấp cơ chế đồng bộ hóaRust về mặc định là re-entrant và bảo đảm an toàn luồng tại thời điểm biên dịch
Nếu không quan tâm đến an toàn luồng tĩnh thì cũng có thể làm dễ như Zig hay C
Khác biệt là Rust cung cấp nhiều công cụ bảo chứng hơn cho hành vi runtime của mã
Khi quay lại các ngôn ngữ khác và thấy người ta dùng thứ này như không có gì, tôi cảm giác đó là chuyện điên rồ về mặt an toàn
Nhưng khi những việc “đơn giản” kiểu này tích tụ lại thì rốt cuộc chẳng còn đơn giản nữa
Rust đã vượt qua ranh giới đó rồi, và giờ thì hoàn toàn không còn trivial nữa
Nếu vậy thì có vẻ hấp dẫn hơn C
Tôi cũng muốn biết khi có hai biến luôn phải được khóa cùng nhau thì xử lý thế nào
Cứ debug một hồi là cuối cùng vấn đề cũng luôn quay về đó
Về bài viết chỉ ra mật độ khái niệm của Rust, tôi nghĩ trên thực tế chỉ cần biết 5% trong số đó là đã có thể dùng một cách năng suất
Tôi đã dùng Rust hơn 12 năm nhưng chưa từng có dịp dùng thứ như
#[fundamental]Rust cũng làm được arena allocation, và cũng có khái niệm allocator
Chỉ là có allocator mặc định, và thường dùng cấp phát heap tường minh như
Box::newBiến toàn cục có thể thay đổi có thể viết kiểu
static FOO: Mutex<T> = Mutex::new(...), và cần mutex để đảm bảo an toàn bộ nhớHệ thống kiểu của Rust được thiết kế không chỉ để đảm bảo an toàn bộ nhớ mà còn cả tính an toàn ngữ nghĩa của mã
Trong C thì độ phức tạp kiểu này ít hơn
Độ phức tạp rốt cuộc vẫn là một vấn đề quan trọng
Đây không chỉ là chuyện có làm được hay không, mà là khác biệt về phong cách lập trình cơ bản
Zig Software Foundation cũng từng trích sai phát biểu về Rust của Asahi Lina
Tôi không thích thái độ marketing kiểu hạ thấp ngôn ngữ khác của Zig
Lý do tôi thích Zig là vì đây là ngôn ngữ có thể xử lý cạn kiệt bộ nhớ một cách thanh lịch
Mọi cấp phát đều được giả định là có thể thất bại (fallible), và phải xử lý một cách tường minh
Không gian stack cũng không bị đối xử như phép màu; trình biên dịch sẽ phân tích đồ thị gọi để suy ra kích thước tối đa
Trong môi trường nhúng, kiểu thiết kế lấy tài nguyên làm trung tâm này là bắt buộc
Xử lý ở cấp độ ngôn ngữ cũng không giải quyết được chuyện đó
Rốt cuộc vẫn mang cùng bài toán quản lý bộ nhớ thủ công
Nếu vậy tôi nghĩ dùng ngôn ngữ có GC còn tốt hơn
Chỉ là thư viện chuẩn của Rust dùng panic khi OOM, nên có hẳn một hệ sinh thái riêng hỗ trợ phát triển nhúng trong môi trường no-std
slice của Go khác với
Vec<T>của Rustappend()trả về một slice mới, có thể chia sẻ vùng nhớ cũ hoặc khôngKhông có cách để giảm bộ nhớ, và nếu chỉ viết
append(s, ...)thì sẽ bỏ qua slice mớiGo có thái độ “cứ làm đúng như tôi nói”, còn Rust là “hãy kiểm chứng xem bạn có làm đúng như tôi nói không”
Tức là Go chấp nhận sai sót để đổi lấy sự đơn giản, còn Rust chọn hướng giảm lỗi dù có phức tạp hơn
Ngoài ra, nếu chỉ viết
append(s, ...)thì sẽ bị lỗi biên dịch, nên nguyên văn đó là một nhận định hơi không chính xácGo là ngôn ngữ rất thận trọng về việc độ phức tạp tăng lên khi thêm tính năng
Có lẽ vì không mấy khi cần tự truyền quanh một growable list
Nhiều trường hợp chỉ là không đọc tài liệu rồi ngạc nhiên
Tôi nghĩ việc bắt UB (Undefined Behavior) của C/C++ bằng kiểm tra runtime là điều khó khả thi trong thực tế
Android cũng đã áp dụng sanitizer cho mọi commit, nhưng chỉ sau khi chuyển sang Rust thì các vụ exploit mới giảm xuống
Tôi thích bài so sánh ngôn ngữ này vì nó bàn khá thẳng thắn về điểm mạnh và điểm yếu của từng ngôn ngữ
Chỉ tiếc là không nhắc đến Raku
Theo tôi, nếu C–Zig–C++–Rust–Go là một dải liên tục của ngôn ngữ mức thấp, thì phía mức cao sẽ là Julia–R–Python–Lua–JS–PHP–Raku–WL
Nó hỗ trợ định nghĩa cú pháp ở cấp độ ngôn ngữ nên rất thuận tiện cho DSL hay phân tích log
Vì chạy trên VM nên hiệu năng thấp hơn, nhưng phù hợp để biểu đạt trực tiếp cấu trúc của bài toán
Với tư cách hậu duệ của Perl, nó hướng đến một ngôn ngữ linh hoạt nhưng nhất quán
Việc nghĩ rằng trong Rust, hàm trả về con trỏ thì sẽ tự động cấp phát heap là một sự hiểu nhầm
Biến cục bộ nằm trên stack và biến mất khi trả về, nên con trỏ sẽ bị vô hiệu
Trong chế độ an toàn, Rust không cho phép dereference con trỏ, còn trong chế độ unsafe thì nhà phát triển phải tự chịu trách nhiệm bảo đảm tính hợp lệ
Có lẽ người ta đã nhầm
Box::newvới “cấp phát ngầm”Điều đó hoặc là do hiểu sai khái niệm, hoặc có vẻ như đang cố tình đánh lạc hướng
Điểm mạnh lớn nhất của Go là mô hình đồng thời đơn giản
Nhờ goroutine mà có thể dễ dàng viết mã song song
Việc tìm triển khai interface thì khó, nhưng độ dễ đọc cao nên rất có lợi cho cộng tác nhóm
Không có colored function, và giao tiếp dựa trên channel đơn giản nên có thể nhanh chóng viết được mã đồng thời đúng đắn
Bài liên quan: Structured Concurrency or Go Statement Considered Harmful
std.Iomới của Zig khá giống mô hình đồng thời của GoTừ khóa
gotương ứng vớistd.Io.async, channel tương ứng vớistd.Io.Queue, cònselecttương ứng vớistd.Io.selectĐiều tôi muốn là một ngôn ngữ kết hợp sự đơn giản của Go với xử lý result/error/enum của Rust và generics tốt hơn
Tôi đã nhìn qua OCaml, D, Swift, Nim, Crystal..., nhưng vẫn chưa có ngôn ngữ nào thực sự thống trị thị trường
Thay vào đó có thể xem Gleam
Hy vọng sẽ có cải tiến giải quyết được kiểu vấn đề lặp đi lặp lại này
Còn generics có lẽ vẫn sẽ là một bài toán khó
Tôi thích tông chung của bài viết vì thể hiện được sự nhiệt tình và tò mò của một lập trình viên mới
Việc Go thiếu generics không chỉ là tối giản cực đoan, mà là kết quả của quá trình cân nhắc trade-off
lifetime của Rust là trở ngại lớn nhất với rất nhiều người, còn tính đổi mới của ngôn ngữ nằm ở sự kết hợp các khái niệm sẵn có
Việc Zig quản lý bộ nhớ thủ công không hẳn là để loại bỏ OOP, mà dựa trên triết lý Data-Oriented Design (DOD)
Bài nói liên quan: Andrew trình bày về DOD
cốt lõi là “chọn lập trình viên chậm, trình biên dịch chậm hay tốc độ thực thi chậm”
Cuối cùng có vẻ nhóm Go đã tìm ra một phương án thỏa hiệp đủ ổn thỏa