2 điểm bởi GN⁺ 4 giờ trước | 2 bình luận | Chia sẻ qua WhatsApp
  • Để giải quyết các khiếm khuyết mang tính cấu trúc của bản triển khai hiện có, nhóm đã viết lại 300.000 dòng Rust sang Zig và đạt mức tương đương tính năng sau 487 ngày, hướng tới bản phát hành đánh số chính thức đầu tiên 0.1.0 vào cuối năm nay
  • Trình biên dịch mới hỗ trợ hot code loading và cross compile có thể tái lập, nội suy chuỗi trong pattern matching, loại bỏ cấp phát heap trong định tuyến HTTP, đồng thời giảm kích thước wasm của Rocci Bird xuống chỉ còn 31KB, tức chưa đến một nửa
  • Lý do cốt lõi để chọn Zig là thời gian build, khả năng kiểm soát allocator và bố trí dữ liệu ở mức chi tiết, hệ sinh thái phù hợp cho phát triển trình biên dịch, cùng khả năng kiểm tra mã không an toàn bộ nhớ; incremental build của Zig 0.17.0 có thể build lại khoảng 460.000 dòng chỉ trong 35ms
  • Trong phân loại lỗi thực tế, trình biên dịch Rust có 21 vụ hỏng bộ nhớ, còn trình biên dịch Zig có 10 vụ, nhưng đa số là do sinh mã sai; lỗi an toàn bộ nhớ của chính trình biên dịch Zig chỉ có 2 trường hợp use-after-free làm hỏng tên tệp
  • Zig rất phù hợp với cấu trúc dữ liệu không dùng con trỏ, giải tuần tự hóa không cần parsing, và việc tái sử dụng bộ tuần tự hóa LLVM bitcode, nhưng về giải phóng bộ nhớ tự động trong test, tính đa hình, trường struct riêng tư, phát hiện mã chết và tính tương thích giữa các bản phát hành thì trải nghiệm phát triển với Rust tốt hơn

Bản viết lại đã đạt tương đương tính năng

  • Nhóm trình biên dịch Roc đã dành khoảng 1 năm rưỡi để viết lại 300.000 dòng Rust sang Zig và đạt mức tương đương tính năng với trình biên dịch cũ
  • Tương đương tính năng là một cột mốc quan trọng, nhưng chưa phải bản phát hành chính thức; 0.1.0 của trình biên dịch mới được nhắm tới vào cuối năm nay
  • Giờ đây đã có thể build game WASM-4 năm 2024 Rocci Bird bằng trình biên dịch mới
    • Toàn bộ game có chưa đến 1.000 dòng mã Roc
    • Mã nguồn đã cập nhật ngắn gọn hơn bản gốc
    • Tệp nhị phân wasm do roc build --opt=size tạo ra có kích thước 31KB, nhỏ hơn một nửa so với đầu ra của trình biên dịch cũ
  • Trên trang chủ, có thể viết và chạy chương trình Roc cơ bản ngay trong trình duyệt bằng trình biên dịch WebAssembly 2,5MB
  • Việc viết lại mất 487 ngày, dài hơn 476 ngày so với 11 ngày Bun chuyển khoảng 500.000 dòng Zig sang Rust
    • Bun là chuyển thẳng, còn Roc gần như là một cuộc tái kiến trúc với nhiều thay đổi lớn về cấu trúc và tính năng của trình biên dịch
    • Vì vậy, khó có thể so sánh trực tiếp quy mô hay thời gian của hai codebase chỉ dựa trên Rust và Zig

Trải nghiệm phát triển mà trình biên dịch mới mang lại

  • Hot code loading và cross compile

    • Chạy server bằng roc server.roc, sau đó sửa mã thì từ request tiếp theo mã mới sẽ tự động được áp dụng
    • Điều này phổ biến ở các ngôn ngữ thông dịch như Python, nhưng không thường thấy ở các ngôn ngữ biên dịch hiệu năng cao như Roc
    • Cơ chế tương tự cũng hoạt động không chỉ với web server mà cả trong các game 2D đơn giản
    • Khi triển khai, roc build server.roc sẽ tạo tệp thực thi độc lập có áp dụng tối ưu hóa LLVM
    • Có thể cross compile tệp nhị phân tĩnh cho Alpine Linux bằng roc build --target=x64musl
    • Nếu đầu vào là cùng một byte mã nguồn, thì trên bất kỳ hệ thống build nào, kể cả Mac, cũng sẽ tạo ra cùng một byte đầu ra
  • Nội suy chuỗi trong pattern matching

    • Hỗ trợ nội suy chuỗi như "/users/${id}" ngay bên trong pattern matching
    • Trình biên dịch xử lý trực tiếp thay vì parse chuỗi mẫu lúc chạy như kiểu định tuyến của Express
    • Có thể match đồng thời HTTP method và đường dẫn, đồng thời phân nhánh bằng pattern cho cả đường dẫn lồng nhau hoặc giá trị mặc định
    • Toàn bộ mã xử lý request HTTP trong ví dụ không thực hiện bất kỳ cấp phát heap nào, đồng thời vẫn kiểm tra tính an toàn kiểu khi biên dịch
    • Định tuyến request HTTP được triển khai không cấp phát nhờ thực thi hàm thuần ở thời điểm biên dịch, và có thể thử cú pháp này trong trình biên dịch WebAssembly trên trang chủ

Lý do viết lại từ đầu

  • Khác với Rust, C và Zig, Roc không phải ngôn ngữ hệ thống mà dùng quản lý bộ nhớ tự động dựa trên đếm tham chiếu
    • Tránh các lần tạm dừng của garbage collector dạng tracing
    • Tận dụng tối ưu hóa Perceus và cơ chế đột biến cơ hội kiểu Koka
  • Không cấp phát heap cho mỗi lần closure capture như các ngôn ngữ không phải hệ thống thông thường, mà dùng defunctionalization đa hình thông qua chuyên biệt hóa tập lambda
  • Defunctionalization cho phép nhiều tối ưu hóa tiếp theo giống như inline trong ngôn ngữ hàm, nhưng việc làm cho bản triển khai cũ trở nên chính xác là cực kỳ khó
  • Thông qua prototype OCaml của Ayaz Hafiz, nhóm xác nhận rằng vấn đề là khiếm khuyết mang tính cấu trúc trải dài qua nhiều giai đoạn của trình biên dịch, và để giải quyết thì phải viết lại phần lớn trình biên dịch
  • Do các cộng tác viên khác cũng đã lên kế hoạch viết lại nhiều phần vì những lý do khác nhau, nên nhóm bắt đầu xem xét viết lại toàn bộ thay vì thay thế dần gần như toàn bộ theo kiểu con tàu Theseus
  • Trong các dự án trình biên dịch thành công, việc viết lại từ đầu, chẳng hạn để self-hosting, là chuyện phổ biến; cũng có trường hợp TypeScript viết lại sang Go chứ không phải ngôn ngữ của chính nó
  • Dù Roc có chủ trương không self-hosting, lần này nhóm đánh giá lợi ích đạt được từ việc viết lại lớn hơn các chi phí đã biết

Tiêu chí chọn Zig thay vì lại dùng Rust

  • Nhóm vốn đã dùng Zig cho nhiều chức năng nền tảng trong standard library, và chỉ thật sự thông thạo Rust và Zig trong các ngôn ngữ hệ thống, nên chỉ nghiêm túc xem xét hai ngôn ngữ này
  • Mỗi dự án phù hợp với một ngôn ngữ khác nhau; việc tiếp tục dùng Rust cho công việc khác và chọn Zig cho Roc không hề mâu thuẫn
  • Thời gian build

    • Các bản build cargo của Rust, kể cả incremental build, đều mất nhiều thời gian, và khi codebase lớn dần thì đây trở thành bất tiện chính
    • Nhóm kỳ vọng chuyển sang Zig sẽ giúp build nhanh hơn đáng kể
  • Kiểm soát bộ nhớ và bố trí dữ liệu

    • Ở mỗi giai đoạn biên dịch, nhóm dùng nhiều allocator khác nhau, đặc biệt là arena allocator, và tận dụng rộng rãi cách bố trí struct-of-arrays (SoA)
    • Hệ sinh thái Rust nhìn chung giả định một global allocator, trong khi hệ sinh thái Zig mặc định truyền allocator một cách chi tiết và cũng hỗ trợ SoA theo tiêu chuẩn
  • Phạm vi hệ sinh thái cần thiết

    • Toàn bộ hệ sinh thái Rust lớn hơn Zig, nhưng các package liên quan đến nhu cầu đặc thù của Roc thì ở cả hai bên đều không nhiều
    • Với những chức năng ngách cần thiết, như sinh bitcode nhanh thay vì bọc thư viện LLVM C++, phía Zig có nhiều lựa chọn hơn
  • Hỗ trợ mã không an toàn bộ nhớ

    • Trình biên dịch Rust cũ với 300.000 dòng có khoảng 1.200 lần dùng unsafe
    • Để so sánh, chính trình biên dịch Rust dùng khoảng 40.000 unsafe trên 3,5 triệu dòng
    • Với trình biên dịch sinh mã máy, các thao tác không an toàn bộ nhớ là một phần quan trọng của công việc
    • Rust theo mô hình cô lập các khối unsafe hiếm gặp rồi kiểm tra bằng Miri hoặc Valgrind, nhưng ở Roc thì unsafe không hề hiếm
    • Zig cung cấp nhiều tính năng hơn để làm cho mã không an toàn bộ nhớ hoạt động đúng, và đây chính là lĩnh vực mà nhóm muốn được hỗ trợ nhiều nhất

Độ an toàn bộ nhớ đạt được mà không cần borrow checker

  • Phạm vi kiểm tra của Rust và Zig

    • Theo tài liệu năm 2019 của Microsoft, khoảng 70% lỗ hổng được xử lý qua các bản cập nhật bảo mật hằng năm là vấn đề an toàn bộ nhớ
    • Nếu chia phân loại năm 2018 trong tài liệu đó theo góc nhìn của Rust và Zig thì như sau
    • 83,6% là đọc/ghi ngoài phạm vi, cast không an toàn, đọc dữ liệu chưa khởi tạo, tràn ngăn xếp và các vấn đề không liên quan đến an toàn bộ nhớ; đây là nhóm không bị ảnh hưởng bởi việc chọn Rust hay Zig
    • 16,4% là use-after-free, thuộc nhóm có thể được phát hiện bởi borrow checker của Rust, ReleaseSafe của Zig và cơ chế kiểm tra kiểu Fil-C
    • ReleaseSafe của Zig sẽ gây panic khi chạy nếu sử dụng bộ nhớ đã được giải phóng
      • Phạm vi kiểm tra kém toàn diện hơn tập con an toàn của Rust
      • Có chi phí khi chạy và chương trình có thể panic
    • ReleaseFast bỏ qua các kiểm tra trong môi trường production, nhưng vẫn giữ chúng trong bản build debug và test
    • Nếu kiểm thử được mọi đường thực thi thực tế thì có thể đạt mức an toàn tương đương ReleaseSafe, nhưng mức bao phủ kiểm thử như vậy thường không thực tế
  • Các dự án Zig khác và unsafe của Rust

    • TigerBeetle dùng ReleaseSafe, và qua kiểm chứng Jepsen rất kỹ đã phát hiện 2 lỗi an toàn, nhưng cả hai đều không liên quan đến an toàn bộ nhớ
    • Ghostty và trình biên dịch Zig dùng ReleaseFast cũng không có CVE nào bắt nguồn từ mất an toàn bộ nhớ trong mã Zig
    • Chương trình Rust cũng có thể xuất hiện khoảng trống về an toàn bộ nhớ thông qua unsafe bên trong dependency
    • Unsafe Rust có rủi ro tương tự Zig ReleaseFast, nhưng không có tính năng tương đương với các kiểm tra khi chạy của Zig để bắt lỗi trong quá trình phát triển
    • Miri và Valgrind có thể giúp ích, nhưng không nhiều dự án Rust sử dụng chúng
    • Thay vào đó, việc chỉ dùng unsafe một cách hiếm hoi và văn hóa rà soát nghiêm ngặt hơn đã tạo nên danh tiếng mạnh về an toàn bộ nhớ của Rust trong thực tế
    • Đã có những trường hợp lỗ hổng liên quan đến unsafe xuất hiện cả trong các dự án viết bằng Rust
      • Deno từng có CVE đọc ngoài phạm vi và use-after-free
      • Rocket từng có CVE use-after-free
      • Actix từng phát sinh nhiều CVE mất an toàn bộ nhớ trong giai đoạn lượng dùng unsafe cao bất thường
    • Roc đánh giá use-after-free không phải rủi ro lớn vì phần lớn cấp phát diễn ra trong arena có vòng đời đơn giản, và cho rằng các kiểm tra bổ sung của Zig hữu ích hơn với phần mã vốn dĩ không an toàn

Các lỗi hỏng bộ nhớ được xác nhận sau khi tái viết

  • Kết quả phân loại issue tracker của Roc bằng Claude Opus 4.8 như sau
Trình biên dịch Có hỏng bộ nhớ Không hỏng bộ nhớ Tổng
Rust 21 2.575 2.596
Zig 10 421 431
  • 21 trường hợp hỏng bộ nhớ của trình biên dịch Rust không phải là hỏng logic bên trong chính trình biên dịch
    • borrow checker đã thực hiện đúng vai trò mong đợi
    • Chúng là các lỗi miscompile khiến mã máy được tạo sai làm hỏng bộ nhớ trong chương trình đã biên dịch
  • Trong 10 trường hợp hỏng bộ nhớ của trình biên dịch Zig, 8 trường hợp cũng là miscompile
  • 2 trường hợp còn lại là use-after-free trong mã báo lỗi
    • Tên tệp trong thông báo lỗi của roc checkroc bundle bị vỡ thành ký tự thay thế U+FFFD
    • Nếu là borrow checker của Rust thì cả hai lỗi này đều đã được phát hiện
  • Ảnh hưởng thực tế đến người dùng nếu chọn công cụ khác sẽ như sau
Lựa chọn công cụ Ảnh hưởng thực tế đến an toàn bộ nhớ
Zig ReleaseFast 2 lỗi khiến tên tệp không hiển thị trong một số thông báo lỗi
Zig ReleaseSafe 2 lỗi khiến một số lỗi bị panic và không thể hiển thị tên tệp
Rust borrow checker Ngăn cả 2 lỗi
  • Xét trong 18 tháng, hàng trăm nghìn dòng mã và hàng trăm báo cáo lỗi, khác biệt giữa ba lựa chọn này về thực chất không lớn đối với dự án
  • Bun xử lý đồng thời các giá trị JavaScript do garbage collector theo dõi và các giá trị được quản lý thủ công, nên use-after-free, double-free và quên giải phóng chiếm tỷ trọng lớn
  • Trình biên dịch Roc không tích hợp với JavaScript hay bộ gom rác có theo dõi nào khác, nên không gặp cùng kiểu vấn đề quản lý vòng đời
  • Roc cần công cụ để tìm lỗi bộ nhớ trong mã đầu ra được sinh ra hơn là lỗi bộ nhớ bên trong chính trình biên dịch, và nhóm sau nằm ngoài phạm vi của borrow checker

Kết quả thực tế về thời gian build

  • zig build --watch -fincremental hiện build lại thay đổi trong khoảng 35ms trên khoảng 450 nghìn đến 460 nghìn dòng mã Zig
  • Zig 0.16.0 bản ổn định có một lỗi làm hỏng -fincremental trong codebase của Roc
    • Bản sửa đã có, nhưng để dùng thì phải chuyển sang bản prebuild Zig 0.17.0 có thay đổi không tương thích ngược
    • Vì cũng phải vendor các dependency liên quan và nâng cấp chúng lên 0.17.0, nên dự án quyết định chờ bản stable tiếp theo
  • Kết quả đo trên máy desktop Intel và Ubuntu 26 như sau
Trình biên dịch Roc Quy mô mã Build nguội Build tăng dần
Bản gốc dựa trên Rust 1.85.0 354K 32,4 giây 10,0 giây
Bản gốc dựa trên Rust 1.97.0 354K 25,4 giây 3,4 giây
Bản Zig 0.16.0 tại thời điểm tương đương tính năng 320K 39,6 giây 8,6 giây
Bản tái viết hiện tại dùng Zig 0.17.0 464K 32,1 giây 0,035 giây
  • Ở thời điểm tương đương tính năng, các đầu ra gần như không đổi cũng bị build lại mỗi lần, còn hiện nay chỉ được tạo lại khi cần
    • Thay đổi này khiến mã tăng khoảng 50%, nhưng build nguội lại nhanh hơn
  • Khi chuyển từ Rust 1.85.0 sang 1.97.0, build tăng dần giảm từ 10 giây xuống 3,4 giây, tức cải thiện khoảng hai phần ba trong 18 tháng
  • Mức 35ms của Zig bằng khoảng 1/100 của 3,4 giây, và đạt được trên codebase lớn hơn khoảng 50% so với phần mã được đo bên phía Rust
  • Hiện tại -fincremental chỉ hoạt động trên x86-64, trong khi nhiều người đóng góp dùng Mac nền tảng ARM, nên lợi thế thời gian build vẫn chưa được tận dụng trọn vẹn

Cấu trúc không con trỏ và giải tuần tự hóa không cần phân tích

  • Bộ nhớ đệm trên đĩa mới sử dụng trực tiếp cách bố trí bộ nhớ tối ưu cho thực thi làm định dạng trên đĩa, giống như cách được dùng trong trình biên dịch Zig và phát triển game
  • Mọi cấu trúc dữ liệu của trình biên dịch đều được biểu diễn bằng mảng chỉ mục 32 bit thay vì con trỏ, và ở nhiều nơi sử dụng dạng mảng struct
    • Giảm mức sử dụng bộ nhớ và tăng tốc độ thực thi
    • Có thể ghi trực tiếp cấu trúc dữ liệu ra đĩa mà không cần chuyển sang định dạng tuần tự hóa riêng
  • Khi giải tuần tự hóa, không phân tích các byte trên đĩa
    • Đọc các byte vào bộ nhớ
    • Sắp xếp lại một phần để các cấu trúc dữ liệu hiện có trỏ tới các mảng mới
    • Sau đó có thể dùng ngay lập tức
  • Tốc độ trên thực tế gần như bị giới hạn bởi tốc độ I/O khi đọc byte từ đĩa vào bộ nhớ
    • Nếu dữ liệu đã có trong bộ nhớ đệm đĩa của hệ điều hành, kết quả build trước đó được nạp lại với tốc độ xấp xỉ memcpy
  • roc check lưu các kết quả như phân tích cú pháp và kiểm tra kiểu xuống đĩa ở lần chạy đầu tiên
    • Nếu mã nguồn đầu vào không thay đổi, ở lần chạy thứ hai dữ liệu cấu trúc sẽ được chuyển thẳng từ đĩa vào bộ nhớ
  • roc test cũng lưu bộ nhớ đệm kết quả kiểm thử của các hàm thuần xác định
  • Bộ nhớ đệm hoạt động theo từng tệp, nên khi thay đổi một tệp thì chỉ tệp đó và các tệp phụ thuộc mới được xử lý lại
  • Cách này khả thi vì toàn bộ trình biên dịch dùng chỉ mục thay cho con trỏ; với cấu trúc phổ biến dựa trên con trỏ, giải tuần tự hóa không cần phân tích là không thể
  • Giới hạn an toàn của cấu trúc dựa trên chỉ mục

    • Cũng như con trỏ có thể trỏ sai địa chỉ, chỉ mục cũng có thể truy vấn sai mảng và đọc các byte tùy ý
    • Rust borrow checker xử lý vòng đời con trỏ, nhưng không kiểm tra chỉ mục nào thuộc về mảng nào
    • Nếu biết trước số lượng mảng cần thiết, compact_arena của Rust có thể tạo type tag bằng macro để ngăn truy vấn sai mảng
    • Với Roc, số lượng mảng thay đổi theo số module nên không thể áp dụng kỹ thuật này, vì vậy compact_arena cũng đánh dấu SmallArena::newunsafe
    • Việc tạo một arena rỗng tự nó không tạo ra rủi ro, nhưng rủi ro thực sự nằm ở thao tác lập chỉ mục mảng được thực hiện rất thường xuyên
    • Safe Rust hiệu quả khi giả định unsafe là nhỏ và được cô lập, nhưng với Roc, nơi unsafe xuất hiện rộng khắp, giả định này không còn đúng

Hệ sinh thái Zig phù hợp với Roc

  • Với Bun, Drop của Rust, vốn chỉ chạy mã dọn dẹp một lần, rất hữu ích vì phải tương tác giữa JavaScript và bộ nhớ quản lý thủ công
  • Ngược lại, Roc muốn dùng arena riêng biệt cho từng module và từng giai đoạn biên dịch, nên các package Rust giả định allocator toàn cục và Drop ngầm trở nên bất tiện
  • Trong hệ sinh thái Zig, API truyền allocator một cách tường minh là phổ biến, nên rất hợp với cách quản lý bộ nhớ của Roc
  • Hệ sinh thái Rust phù hợp hơn với cấu trúc Bun muốn, còn hệ sinh thái Zig phù hợp hơn với cấu trúc Roc muốn
  • Tái sử dụng bộ tuần tự hóa LLVM bitcode

    • LLVM là phụ thuộc cốt lõi của trình tối ưu hóa Roc và thực hiện tối ưu hóa bổ sung sau khi Roc tự tối ưu hóa
    • LLVM thường xuyên phá vỡ tương thích API quan trọng, nên việc nâng cấp phiên bản tốn khá nhiều thời gian và chi phí
    • Định dạng bitcode được tuần tự hóa của LLVM thì ổn định và tương thích ngược, nên nếu dùng bộ tuần tự hóa riêng có thể tránh được các thay đổi của API C++
    • Để làm vậy, cần một bộ tuần tự hóa bitcode viết thủ công, tách rời khỏi thư viện LLVM C++
    • Triển khai đã được biết đến trước đó nằm trong trình biên dịch Zig, và trình biên dịch mới của Roc tái sử dụng phần mã Zig đó
    • Nguồn phụ thuộc lớn nhất mà Roc nhận từ hệ sinh thái Zig không phải là các package thông thường mà là chính trình biên dịch Zig

Những tính năng Rust còn nhớ khi ở Zig

  • Trong việc hiện thực trình biên dịch cần kiểm soát cấp phát tường minh, nhưng trong kiểm thử thì cấp phát/giải phóng tự động của Rust lại tiện hơn
    • Allocator dùng cho kiểm thử của Zig có thể phát hiện rò rỉ bộ nhớ, và cũng phát hiện được rò rỉ trong mã Roc đã biên dịch
    • Đổi lại, ở mỗi bài kiểm thử phải viết chính xác initdefer deinit; chỉ cần sai một chỗ là kiểm thử sẽ thất bại vì rò rỉ
  • comptime của Zig có phần chồng lấp với đa hình tham số và đa hình ad-hoc, nhưng vẫn thấy nhớ cả hai dạng đa hình đó
    • Trait Allocator của Rust có thể nhận self
    • Các triển khai như ArenaAllocator của Zig phải nhận con trỏ anyopaque rồi ép kiểu về đúng kiểu của chính nó
  • Không có trường struct riêng tư, nên không thể dùng lỗi biên dịch để chặn truy cập trực tiếp vào các trường lẽ ra không nên được truy cập
    • Khi xem xét diff mã, chỉ thấy truy cập trường mà không thấy tài liệu của struct gốc, nên lần nào cũng phải kiểm tra riêng
  • Đôi khi vẫn nhớ tính nhất quán của Rust khi hàm, biến và hằng số đều dùng snake_case
  • unsafe và borrow checker có cái giá phải trả, nhưng cũng mang lại cảm giác yên tâm vì chỉ cần lo một số vấn đề nhất định bên trong các khối unsafe
    • Điều này không có nghĩa là cần thêm tính năng tương tự vào Zig
  • Trong Zig, việc phát hiện mã chết muộn hơn so với Rust xảy ra khá nhiều
    • Công cụ tích hợp của Zig và tidy.zig của TigerBeetle cũng không bắt được một phần mã chết
    • Mã chết không được sinh vào binary nên không ảnh hưởng tới người dùng, nhưng lại bất lợi cho việc quản lý codebase
  • Trong Rust, việc nâng cấp phiên bản nhỏ và thay đổi edition phần lớn đều diễn ra suôn sẻ
  • Hiện tại Zig không hướng tới tương thích ngược, và đây là điều đã được dự đoán nên không phải vấn đề lớn, nhưng trải nghiệm nâng cấp đơn giản của Rust vẫn tốt hơn

Những điểm hài lòng ở Zig

  • Zig có sức hút của một thiết kế theo hướng cắt giảm, giảm bớt các công cụ quen thuộc kiểu lập trình hàm để đổi lấy những thuộc tính khác
  • Không có macro, và nhiều vấn đề, bao gồm đa hình tham số, có thể được giải quyết bằng comptime hoặc hàm thông thường
  • Có thể kiểm soát bố trí dữ liệu rất chi tiết
    • Dùng được các kiểu số nguyên không phải lũy thừa của 2 như u7, u5 mà không cần xử lý bit riêng
    • Hỗ trợ packed struct mặc định
    • Có thể inline hàm ở vị trí gọi thay vì vị trí khai báo
    • Có thể dùng các tính năng mà trong Rust cần crate dựa trên macro, nhưng ở đây không cần thêm phụ thuộc
  • Chuỗi công cụ build của Zig rất phù hợp để tạo binary độc lập cho Alpine Linux và WebAssembly
    • Nó cũng xử lý được kiểu build đặc biệt là biên dịch các builtins tương ứng với thư viện chuẩn của Roc thành blob nhị phân opaque rồi nhúng vào file thực thi cuối cùng
    • Uber cũng dùng chuỗi công cụ Zig trong hạ tầng build dù không dùng ngôn ngữ Zig
  • Trong cơ chế xử lý lỗi của Zig, cả việc cấp phát heap thất bại cũng được xem là lỗi không gian người dùng thông thường
  • Roc cũng dùng một chiến lược tương tự, tận dụng kiểu hợp ẩn danh và payload để lỗi tích lũy một cách tự nhiên
  • Tác giả thích cách làm của Zig và Roc hơn so với anyhow, thiserror và cách xử lý dựa trên Result mặc định của Rust
  • Về cú pháp lan truyền lỗi, tác giả thích toán tử hậu tố ? của Rust hơn try của Zig, nên Roc cũng chọn toán tử hậu tố ?
  • Xét toàn bộ dự án, bao gồm API allocator chi tiết và mã tái sử dụng cho trình biên dịch hiệu năng cao, tác giả rất hài lòng với lựa chọn Zig

Các bước tiếp theo của Roc

  • Dự kiến phát hành 0.1.0 của trình biên dịch mới vào nửa sau năm nay, đây sẽ là bản phát hành được đánh số đầu tiên của Roc
  • Ngay cả trước khi phát hành, vẫn có thể thử bản dựng Nightly, nhưng hiện vẫn còn nhiều lỗi, tính năng chưa hoàn thiện và tài liệu chưa đầy đủ
  • Roc Programming Language Foundation là tổ chức phi lợi nhuận 501(c)(3) tại Mỹ, và tiền quyên góp chủ yếu được dùng để trả thù lao cho những người đóng góp
  • Có thể theo dõi tiến độ phát triển trong tương lai và đặt câu hỏi tại Roc Zulip

2 bình luận

 
Các ý kiến trên Lobste.rs
  • Nhìn trên số liệu, 35ms của Zig nhanh hơn gần 100 lần so với 3,4 giây của Rust và mã cũng nhiều hơn khoảng 50%, nhưng trong phát triển thực tế có vẻ sự khác biệt đó bị phóng đại
    Nếu không build lại trình biên dịch mỗi phút mà build mỗi 10 phút, thời gian tiết kiệm được chỉ khoảng 3 giây, và 3,4 giây cũng đã đủ nhanh
    Khi codebase lớn lên Rust có thể chậm hơn, nhưng thời gian cold build lại tệ đi; cộng thêm việc thay laptop mỗi 2 năm và các cải tiến của chính trình biên dịch, khó có thể khẳng định thời gian build của Rust sẽ tiếp tục tăng trong dài hạn

    • Việc lỗi biên dịch và kết quả test đã bị vô hiệu hóa xuất hiện ngay khi lưu, trước cả khi nhấn phím tiếp theo hoặc chuyển vị trí chỉnh sửa, là một lợi thế cực lớn
      Chế độ check và LSP của Rust nhanh hơn biên dịch lại toàn bộ, nhưng không đạt cùng mức
    • Giả định chỉ biên dịch lại một lần mỗi 10 phút sẽ đánh giá thấp sự khác biệt
      Dùng tổ hợp --watch -fincremental có thể biên dịch lại mỗi khi lưu, đem lại phản hồi rất nhanh và thường xuyên
      Tôi đã chuyển dự án cá nhân từ Rust sang Zig và cũng đã dùng Rust nhiều năm trong công việc; tốc độ biên dịch của Zig thật sự là một thay đổi giúp dễ thở hơn
  • 35ms thì nhanh đến đáng kinh ngạc
    Chỉ relink thôi tưởng cũng lâu hơn thế, nên tôi tò mò trình biên dịch thực sự đang làm gì

    • Zig có linker riêng
    • Mấu chốt là link tăng dần và cách sửa binary hiện có ngay tại chỗ; tôi nhớ trong trình biên dịch Roc họ gọi đó là surgical linking
      Nếu thay đổi phần triển khai của một hàm thì chỉ cần chèn assembly mới vào binary hiện có, nhưng inlining, thay đổi chữ ký hàm, dependency, hay tái bố trí khi thiếu chỗ đều cần phân tích thêm
    • Nhóm Zig đã phụ trách nhiều công việc về biên dịch tăng dần, và ELF linker mới tuy chưa hoàn thiện nhưng được thiết kế với mục tiêu link tăng dần ở đơn vị từng hàm và biến toàn cục
      Khi sửa hoặc thêm hàm, nó tìm một vùng trống đủ lớn trong section .text của file thực thi đầu ra rồi ghi mã máy mới vào đó
      Nếu không đủ chỗ, có thể phải mở rộng .text và di chuyển dữ liệu khác, nhưng họ mở rộng section theo cấp số nhân để phân bổ chi phí
      Khi cần, symbol table và relocation entry cũng được thêm vào; vì cũng chừa sẵn vùng trống cho những phần này, nên sau khi thao tác xong chỉ cần đóng file là kết thúc
      Ước tính dựa trên kết quả Tracy tôi thấy gần đây, trong tổng 35ms thì linker chỉ dùng khoảng 1ms
  • Con số dùng unsafe khoảng 1.200 lần trong 300 nghìn dòng Rust nhiều hơn tôi dự đoán
    Ví dụ Inko chỉ có 162 biểu thức unsafe { ... }, và 87 hàm unsafe được lộ ra cho mã sinh thông qua C ABI, trong khi tổng mã Rust khoảng 88 nghìn dòng
    Tuy nhiên, do cách Inkwell hoạt động, toàn bộ backend LLVM về cơ bản là một vùng unsafe khổng lồ, nên khó rút kết luận chỉ từ so sánh đơn giản
    Tôi chỉ đánh dấu unsafe cho những hàm thực sự gây hại đến an toàn bộ nhớ, như thao tác trực tiếp với con trỏ, nhưng lập trình viên khác cũng có thể đánh dấu như một gợi ý rằng cần làm một số việc trước để tránh panic

  • Con số unsafe xuất hiện 40 nghìn lần trong thư viện chuẩn và trình biên dịch Rust là không chính xác
    Đó là kết quả tính cả các từ trong test và chú thích; phần lớn nằm trong thư viện chuẩn, còn trong chính trình biên dịch thì kể cả chú thích và test cũng dưới 2 nghìn lần
    Trong thời gian đóng góp cho rustc, tỷ lệ PR của tôi có chứa unsafe còn chưa tới 1%, cho thấy mã không an toàn bên trong trình biên dịch khá hiếm
    Việc thư viện chuẩn, nơi triển khai runtime nền tảng mà toàn bộ Rust phụ thuộc vào, có nhiều unsafe là điều tự nhiên; loại mã này trong ngôn ngữ nào cũng không an toàn, dù được thể hiện tường minh hay ngầm định
    Trình biên dịch đó nhỏ hơn rustc khoảng 10 lần nhưng lượng dùng unsafe lại tương tự, nên khó nói là tràn lan, song chắc chắn sẽ gặp thường xuyên; tôi tò mò vì sao nó cần nhiều hơn rustc đến vậy

    • Codebase của chúng tôi cũng trộn cả trình biên dịch và thư viện chuẩn, và một phần thư viện chuẩn được triển khai bằng hàm nội tại của trình biên dịch, nên theo chỉ số đó có thể xem là đang so sánh các đối tượng tương tự
      Chúng tôi chưa phân tích riêng phần unsafe liên quan đến thư viện chuẩn trong hai codebase, nhưng nếu triển khai trình biên dịch mới bằng Rust, tôi dự đoán sẽ có nhiều unsafe hơn rustc do cấu trúc dùng caching và chỉ mục thay cho con trỏ
      Chúng tôi không có ý hạ thấp công việc của nhóm Rust; mục đích là trình bày lựa chọn và tiến độ của mình trong khi vẫn tôn trọng thành quả của các dự án khác
  • Đây là bài viết đánh giá cân bằng, không che giấu ưu nhược điểm của cả Rust lẫn Zig; đặc biệt thành thật ở kết luận rằng ngay cả sau 18 tháng viết hàng trăm nghìn dòng mã và xử lý hàng trăm lỗi, nếu chọn một ngôn ngữ khác trong số các phương án thì kết quả dự án có lẽ cũng không khác biệt đáng kể
    Tuy nhiên, phần giải thích rằng trong các trình biên dịch sinh mã máy như Roc hay rustc, các thao tác không an toàn về bộ nhớ chiếm một phần lớn công việc thì khá khó hiểu
    Cũng có nhiều trình biên dịch được viết bằng OCaml hoặc Haskell, và bản thân việc sinh mã máy chỉ là sắp xếp các byte vào một vector rồi ghi ra tệp, nên không có lý do gì phải không an toàn
    Nếu là thông dịch hoặc biên dịch JIT thì có thể hiểu được, nhưng tôi tò mò vì sao điều đó cũng cần thiết trong biên dịch thông thường

    • Bản thân việc sinh mã máy chỉ đơn giản là ghi các byte, nên cách nói rằng nó không an toàn là không chính xác
      Rủi ro phát sinh khi thực thi mã máy đã được sinh ra, và vì trình biên dịch thực tế thường vừa sinh mã máy vừa thực thi nó, nên tôi đã diễn đạt là một phần lớn công việc
      Ngoài thông dịch và JIT, các trường hợp này còn bao gồm việc đánh giá mã người dùng tại thời điểm biên dịch, như const fn của Rust hoặc các biểu thức có thể được kéo lên cấp cao nhất trong Roc, hoặc chạy kiểm thử rồi kiểm tra đầu ra để quyết định nội dung sẽ hiển thị cho người dùng
    • Có vẻ ý là để tạo một trình biên dịch tăng dần rất nhanh như trình biên dịch Zig thì cần ngôn ngữ cấp thấp và khá nhiều thao tác không an toàn
    • Trong bài viết, cả những trường hợp lỗi an toàn bộ nhớ phát sinh do biên dịch sai cũng được tính là lỗi an toàn bộ nhớ của trình biên dịch
      Dù bộ nhớ bị hỏng nằm trong tiến trình trình biên dịch hay trong chương trình được sinh ra, tiêu chí là nguyên nhân khiến bộ xử lý hoạt động sai là các lệnh do trình biên dịch tạo ra, và nơi cần sửa cũng là mã của trình biên dịch
    • Ngay cả trong giai đoạn trình biên dịch của Roc còn được viết bằng Rust, Roc vẫn dùng Zig cho runtime và thư viện chuẩn
      Các thành phần như mã liên kết bên ngoài và bộ thu gom rác cũng cần rất nhiều unsafe trong Rust, làm triệt tiêu đáng kể lợi thế của Rust an toàn
      Có thể chỉ viết trình biên dịch bằng một ngôn ngữ an toàn, nhưng hiệu năng là vấn đề; Zig và trình biên dịch Roc sử dụng rộng rãi cấu trúc mảng (SoA) và chỉ số mảng thay cho con trỏ
      Nếu triển khai điều này bằng Rust thì sẽ phải vòng qua borrow checker, làm mất các lợi ích an toàn liên quan
      Quan hệ vòng đời trong trình biên dịch hóa ra đơn giản đến bất ngờ: ở một giai đoạn, có thể cấp phát, tạo và sửa dữ liệu trong arena; chuyển sang giai đoạn tiếp theo dưới dạng chỉ đọc; rồi hủy toàn bộ arena
      Phần phức tạp hơn là biên dịch tăng dần và liên kết, trong đó đọc trạng thái từ đĩa và sửa binary tại chỗ; quá trình này có thể gây hỏng trạng thái, lỗi, biên dịch sai và vấn đề bộ nhớ, nhưng tách biệt với an toàn bộ nhớ của chính tiến trình trình biên dịch
      An toàn và tính đúng đắn là khái niệm rộng hơn rất nhiều so với an toàn bộ nhớ bên trong chương trình, đặc biệt khi cần thực hiện một cách an toàn và đúng đắn những tác vụ khó biểu đạt bằng Rust
  • Thật vui khi được giới thiệu là tác giả của compact_arena, nhưng cách bài viết mô tả việc thư viện này dùng unsafe là không đúng
    Mục đích của compact_arena là bảo đảm không thể lạm dụng new trong Rust an toàn, từ đó làm cho việc lập chỉ mục trở nên an toàn
    Macro mk_arena có thể được gọi từ mã Rust an toàn ngay cả bên trong vòng lặp khi không biết trước số lượng arena cần thiết

    • Có thể tôi chưa hiểu phần giải thích rằng new được dùng an toàn, nhưng trong mục an toàn của https://docs.rs/compact_arena/0.5.0/… có ghi rằng tag truyền vào constructor là nền tảng của kỹ thuật lập chỉ mục, và nếu dùng giá trị này với arena khác thì chỉ số của hai arena sẽ bị trộn lẫn, dẫn đến truy cập ngoài phạm vi và hành vi không xác định
      Ý của tôi không phải là bản thân lời gọi new(), mà là rủi ro phát sinh khi lập chỉ mục trên giá trị được tạo bởi new(); và tôi cho rằng nơi cần audit và đánh dấu unsafe cũng là chính chỗ gọi lập chỉ mục thực tế
      Tuy nhiên, cũng có khả năng tôi đã hiểu sai thiết kế
  • Theo tôi biết, với Python, để phản ánh mã mới thì phải khởi động lại chương trình, còn hot reloading cần các phần mở rộng không chuẩn và phải viết mã cẩn thận
    Hot reloading phổ biến hơn trong các môi trường phát triển dựa trên image như Lisp hoặc Smalltalk, hoặc trong Erlang

    • Dùng importlib.reload() thì Python cũng có thể hot reload, nhưng tôi không rõ nó được dùng rộng rãi đến mức nào
  • Tôi tò mò liệu có thể giảm thời gian build nếu không dùng cargo build hay không
    Không chắc việc cấu hình Bazel lớn hơn hay nhỏ hơn so với chuyển sang Zig, và tất nhiên thời gian build không phải là lý do duy nhất cho việc đổi ngôn ngữ

 
Ý kiến trên Hacker News
  • Nhìn chung bài viết ổn, nhưng khó đồng ý với cách nói rằng trong các trình biên dịch tạo mã máy như Roc hay rustc, các thao tác không an toàn về bộ nhớ chiếm phần lớn công việc
    Việc vá nhị phân khi đang chạy hoặc nạp lại mã có thể cần mã không an toàn, nhưng trong quy trình tạo tệp thực thi thông thường thì bản thân việc sinh mã máy không có lý do gì phải là không an toàn
    Ngược lại, khả năng gặp mã không an toàn trong runtime của ngôn ngữ còn cao hơn

    • Đồng ý. Sinh mã máy chỉ là việc ghi ra các byte, và tính không an toàn tiềm ẩn chỉ xuất hiện khi thực thi mã máy đó
      Tuy vậy trên thực tế nhiều trình biên dịch vừa tạo mã máy vừa thực thi trực tiếp, nên mới diễn đạt là “chiếm phần lớn công việc”, nhưng không có nghĩa trình biên dịch bắt buộc phải làm cả hai việc
      Ý ở đây còn bao gồm đánh giá mã người dùng tại thời điểm biên dịch, như const fn của Rust hay các biểu thức có thể được kéo lên top-level trong Roc, cũng như chạy test rồi kiểm tra đầu ra để quyết định nội dung hiển thị
    • Nhiều người thường vin vào chuyện ngay cả ngôn ngữ an toàn bộ nhớ cũng có các khối mã không an toàn để bẻ lái sang kết luận kiểu “thế thì an toàn bộ nhớ có ích gì”
      Lập luận này cũng giống như nói rằng vì dù thắt dây an toàn vẫn có thể đập đầu, nên dây an toàn chỉ phiền phức và không nên dùng
    • Việc sinh mã máy không đòi hỏi thao tác không an toàn như một bản chất tất yếu, nhưng tôi nghĩ ở đây phản ánh những ưu tiên khác nhau
      Với mã hiệu năng cực hạn, từ cấu trúc dữ liệu, thuật toán cho tới chiến lược cấp phát bộ nhớ đều sẽ khác đi, và TigerBeetle nổi tiếng với cách cấp phát toàn bộ bộ nhớ ngay từ lúc khởi động
      Trình biên dịch Roc cũng muốn chọn những đánh đổi tương tự Zig, nên việc thấy nhiều mẫu hình chung là điều tự nhiên
    • Nói rằng sinh mã máy cần thao tác không an toàn thì rõ ràng là sai. Trình biên dịch hoàn toàn có thể làm việc chỉ với các cấu trúc dữ liệu được trừu tượng hóa đầy đủ, và nếu có chỗ nào thực sự dễ cần tới thao tác không an toàn về bộ nhớ thì nó gần với linker hơn
    • Thực sự khó hiểu là trong phần nào của trình biên dịch, để tạo ra mã máy, lại cần đến các phép toán không an toàn về bộ nhớ
  • Không thấy có cơ sở cho nhận định rằng ReleaseSafe dùng kiểm tra runtime để bắt use-after-free
    Tôi đã xem tài liệu Zig về các kiểm tra an toàn bộ nhớ runtime nhưng không tìm được nội dung liên quan khi tìm use-after-free, UaF, hay safety-checked; ngay cả khi dùng DebugAllocator trong bản build phát hành thì cũng không phát hiện việc này một cách đáng tin cậy
    Bài viết liên quan được tổng hợp tại https://landaire.net/memory-safety-by-default-is-non-negotia...

    • Với tư cách là người đã viết khá nhiều mã Zig, kể cả có tính năng đó thì trong mã của tôi nó cũng chưa từng hoạt động ra hồn
      Đây là đánh giá tôi có được từ việc tự tay viết code trước thời AI, dù có thể khi kết hợp Zig với LLM thì có gì đó khác đi, nhưng càng đọc bài lại càng thấy thêm những nhận định diễn đạt kỳ lạ nên khó tin tưởng
      Bài này nghe giống một bài viết nhằm hợp thức hóa một cuộc tranh cãi có sẵn hơn là một đánh giá kỹ thuật chân thành, nhưng vì tôi thích những bài viết và ngôn ngữ khác thường, đồng thời cũng có phần phản cảm với làn sóng AI thái quá, nên tạm thời vẫn muốn nhìn nó theo hướng thiện chí
    • Theo tôi biết, ReleaseSafe chủ yếu thêm kiểm tra biên và gây panic ở những đoạn mã không thể tới được
      Có vẻ Zig không cung cấp temporal memory safety
  • Việc một OCaml đã trưởng thành, linh hoạt và đủ giàu sức biểu đạt được dùng để kiểm chứng nguyên mẫu nhưng không được chọn làm ngôn ngữ triển khai cuối cùng là điều khá thú vị
    Cũng nghi ngờ liệu incremental build của Zig có thực sự nhanh hơn dune một cách đáng kể hay không, và dù cross-compilation là một ưu điểm, phần “vì sao là Zig” lại không nhắc tới điều đó
    Tôi cũng tò mò liệu khả năng kiểm soát bộ nhớ chi tiết có thực sự quan trọng với trình biên dịch đến vậy không, và giống như Rust hay WASM từng khởi đầu với OCaml, các maintainer thường quyết định chuyển sang ngôn ngữ khác ở cột mốc nào

    • Rust đã rời OCaml khi quyết định tự viết lại bằng Rust. Bài viết cũng ngầm gợi đây là thời điểm thường gặp để viết lại toàn bộ dự án, và tôi đồng ý với nhận định đó
    • Một trong những mục tiêu cốt lõi của Roc là tốc độ biên dịch. Tôi đoán OCaml bị loại vì không phải là ngôn ngữ lập trình hệ thống
  • Incremental build của Zig rõ ràng là một tính năng mang tính quyết định, và tôi hiểu lựa chọn đổi ngôn ngữ để có được nó trong ngắn hạn
    Nhưng ở trung hạn, tôi kỳ vọng Rust cũng sẽ sớm có tính năng tương tự trong tương lai gần
    Tôi muốn tốc độ, nhưng không muốn đi nhanh rồi tự chuốc họa vào thân, và hiện đang tự làm một ngôn ngữ kết hợp sự an toàn của Rust, các tính năng của Zig, và runtime Go không có garbage collection

    • Các bản build Rust nhanh hơn đã được triển khai tại https://rust-lang.github.io/rust-project-goals/2026/roadmap-..., và phần lớn mục tiêu trên trang này đều nhắm tới năm nay
    • Khả năng cao là thời gian biên dịch của Rust sẽ được cải thiện trước rất lâu so với việc Zig trở nên an toàn hơn
    • Tôi tự hỏi thay vì chờ trình biên dịch Rust nhanh hơn, liệu có thể thêm một dạng borrow checker vào Zig hay không
      Hướng này có vẻ khả thi hơn, thậm chí có thể triển khai ở user space
    • Tôi tò mò ngoài việc không có garbage collection thì runtime Go còn có ưu điểm đặc biệt nào
    • Thỉnh thoảng tôi dùng số token Codex còn dư để thử kết hợp sự an toàn của Rust với các tính năng của Zig tại https://github.com/ityonemo/clr
  • Nếu đã thực hiện một so sánh mang tính khoa học thực sự trong quá trình chọn ngôn ngữ để viết compiler thì có lẽ sẽ thuyết phục hơn nhiều
    Có vẻ họ xuất phát từ giả định chưa được kiểm chứng rằng compiler hiệu năng cao cần một ngôn ngữ hệ thống cấp thấp (https://www.roc-lang.org/faq#self-hosted-compiler), rồi đi đến kết luận rằng ngoài Rust thì lựa chọn duy nhất là Zig
    Hiệu năng compiler bị chi phối bởi thuật toán; ngay cả các ngôn ngữ managed nhanh nếu dùng cùng thuật toán thì nhìn chung thời gian chạy thường cũng chỉ trong khoảng gấp đôi, trong khi chênh lệch hiệu năng do khác biệt thuật toán thì gần như không có giới hạn
    Bản thân Zig là một phản ví dụ cho lý thuyết rằng viết compiler bằng ngôn ngữ cấp thấp sẽ khiến nó nhanh hơn, và mức khoảng 15 nghìn dòng mỗi giây của Roc không phải là nhanh. Còn có tài liệu cho thấy ngay từ năm 1998, compiler ML đã xử lý được 3 nghìn dòng mỗi giây (https://flint.cs.yale.edu/cs421/case-for-ml.html)
    Có thể sẽ hữu ích hơn cho tương lai nếu tạm dừng công việc compiler hiện tại và tạo một compiler tự host cho một tập con Roc thật nhỏ có thể làm trong dưới 10 nghìn dòng
    Khi đó, thay vì một implementation 300 nghìn dòng, có thể thử nhiều implementation khác nhau ở quy mô 10 nghìn dòng và kiểm chứng liệu ngôn ngữ cấp thấp có thực sự cần thiết cho mục tiêu hiệu năng hay không
    Trong quá trình tự host, các tính năng Roc thực sự quan trọng sẽ bộc lộ rõ hơn, cũng sẽ viết được nhiều mã Roc hơn, và nếu cải thiện các tính năng đa dụng cần cho compiler thì các ứng dụng tầng dưới cũng sẽ cùng được hưởng lợi

    • Nhưng nếu đòi hỏi tính khoa học thì chính đánh giá này cũng khá võ đoán và không có nhiều cơ sở thực chứng
      Chỉ riêng việc ML đã từng được compile nhanh vào thập niên 1990 thì khó có thể dùng để đánh giá tốc độ compile của Roc ngày nay. Thiết kế ngôn ngữ tạo ra những ràng buộc rất mạnh lên các thuật toán cần dùng, và phần cứng hiện đại cũng phức tạp hơn nhiều
      Roc có một mức độ overloading nhất định và dường như cũng có những thuật toán tinh vi để tránh cấp phát closure trên heap; các yêu cầu như vậy có thể tạo ra độ phức tạp thuật toán không thể loại bỏ
      Khi đã chạm tới giới hạn tối ưu thuật toán thì thứ còn lại là giảm hệ số hằng số; và đặc biệt với các ngôn ngữ cấp cao có quản lý bộ nhớ, điều đó rõ ràng tạo ra một mức đáy cho việc có thể hạ thấp hệ số này đến đâu
      Tôi đã thấy những trường hợp trong code thực tế mà việc trực tiếp kiểm soát bố cục bộ nhớ giúp cải thiện hiệu năng hơn 10 lần, và trong ngành game thì kiểu công việc này đôi khi chiếm phần lớn sự nghiệp. Một môi trường nơi chỉ cần một thuật toán khéo léo là mọi vấn đề hiệu năng đều biến mất thì khá xa rời thực tế
  • Build Rust là một trong những nguyên nhân gây lãng phí lớn dung lượng lưu trữ trên hầu hết mọi máy tính; chỉ cần build vài thư viện là hàng chục GB có thể tích lại rất nhanh
    Có thể cấu hình thư mục build toàn cục để tái sử dụng dependency giữa các project, nhưng dù là giải pháp nào thì tốt hơn vẫn nên được cung cấp như hành vi mặc định

    • Đây là kiểu đánh đổi dung lượng đĩa để lấy tốc độ build; trong một số trường hợp, dùng thêm dung lượng còn có thể tăng tốc hơn nữa
      Tuy vậy vẫn cần dọn rác cache, và hiện đang gần hoàn tất một thay đổi mới về cách bố trí các output build trung gian để việc này trở nên dễ dàng hơn
    • Nghĩ đến việc kích thước node_modules luôn bị chỉ trích trong hệ sinh thái JavaScript thì đây là một sự đối lập khá thú vị
      Ngay cả trong một project Tauri mà code backend còn nhỏ hơn nhiều so với frontend, output build Rust vẫn là 9GB, còn node_modules chỉ có 550MB
  • Tôi đang hiểu Roc như một ngôn ngữ scripting được nhúng vào C ABI, và tôi tò mò về lĩnh vực ứng dụng thực tế của nó
    Không rõ họ có định cạnh tranh với WASM trong môi trường plugin cung cấp một nền tảng Roc lớn hay không, và tại sao nhà phát triển ứng dụng lại có lý do để lộ ra một lớp Roc thay vì dùng WASM, nơi người viết plugin có thể dùng bất kỳ ngôn ngữ nào
    Nếu đó là một ngôn ngữ ở cấp độ ứng dụng dùng một nền tảng Roc nhỏ, thì tôi cũng muốn biết liệu họ có định cạnh tranh với Gleam cho mã HTTP phía server và với Elm cho mã phía client hay không

    • Tôi đặc biệt thích tìm hiểu xem những ngôn ngữ lạ, nhất là ngôn ngữ hàm, được dùng cho loại ứng dụng nào, nhưng ngay cả sau khi đọc roc-lang.org và FAQ tôi vẫn không nắm được Roc sẽ được dùng cụ thể vào việc gì
  • Thời gian compile là một yếu tố bị đánh giá thấp nghiêm trọng. Việc phải chờ build C++ 10 phút mỗi lần là điều tôi khó chịu nhất, và nó phá vỡ hoàn toàn dòng tập trung khi phát triển

    • Tôi dùng Rust-Analyzer trong VS Code để phân tích cú pháp và tự động hoàn thành, nhưng nó quá chậm vì cứ mỗi lần lưu file lại chạy compiler để cập nhật thông tin
      Chỉ cần chuyển từ file .rs sang file .ts là đã có cảm giác như vừa thay hẳn sang một chiếc máy tính khác
  • Tôi không trực tiếp dùng Zig, nhưng vẫn rất kỳ vọng vào một vài khả năng của nó
    Đặc biệt là các game mới được viết bằng Zig dễ dùng hơn C, phần mềm phân tán mà TigerBeetle đã cho thấy tiềm năng, và lĩnh vực robotics mà cá nhân tôi quan tâm rất nhiều