3 điểm bởi GN⁺ 2023-10-26 | 1 bình luận | Chia sẻ qua WhatsApp
  • Sau khi chuyển từ JavaScript sang Rust để tập trung vào WebAssembly, tác giả đã đánh giá giá trị thực tế của Rust qua 3 năm xây dựng Wick, triển khai production, viết ebook và phát hành khoảng 100 gói trên crates.io
  • borrow checker, hệ thống kiểu phong phú, các mẫu hàm chức năng và việc không có null giúp chặn nhiều lỗi ngay từ lúc biên dịch, từ đó duy trì codebase lớn với ít test hơn
  • Clippy và Cargo workspace rất mạnh, nhưng những khoảng trống trong công cụ và hệ sinh thái như cấu hình lint toàn cục hay phát hành workspace lại dẫn đến chi phí vận hành
  • async, refactor, và việc quản lý generic·lifetime·trait constraint vẫn là những vùng có ma sát lớn hơn so với JavaScript hay Go
  • Rust vững chắc và đa dụng, nhưng chi phí tuyển dụng, học tập, lặp nhanh và truy vết vấn đề cao, nên phù hợp hơn khi phạm vi đã rõ hoặc có thể chấp nhận chi phí ban đầu lớn

WebAssembly đã dẫn tới quyết định chọn Rust

  • Vài năm trước, tác giả gác lại công việc hiện có để tập trung 100% vào WebAssembly, và khi đó Rust có hỗ trợ biên dịch sang WebAssembly tốt nhất
  • Các runtime WebAssembly giàu tính năng khi ấy cũng đều dựa trên Rust, nên trong các lựa chọn có sẵn thì Rust là phương án thực tế nhất
  • Sau đó tác giả đã tạo ra Wick, một application framework kiêm runtime dùng WebAssembly làm hệ thống module cốt lõi
  • Trong 3 năm, kinh nghiệm với Rust được tích lũy qua nhiều triển khai production, một ebook, và khoảng 100 package phát hành trên crates.io

Duy trì nhiều code hơn với ít test hơn

  • Trong Rust, tác giả bắt đầu viết test như ở các ngôn ngữ thông thường, rồi nhận ra mình đang viết những test không thể thất bại nếu code đã biên dịch được
  • Nếu tránh các khối unsafe {} và những method dễ gây panic như .unwrap(), nhiều vấn đề sẽ mặc định được né tránh
  • borrow checker, hệ thống kiểu phong phú, các mẫu và thư viện mang phong cách hàm chức năng, cùng việc không có giá trị null, giúp giảm công sức dành cho test
  • Dự án Wick với hơn 70.000 dòng code đã được duy trì bằng lượng test ít hơn rất nhiều so với mức cần thiết ở các ngôn ngữ khác
  • Khi cần test, nhờ integration test harness của Rust mà có thể thêm test ngay cạnh code rất dễ dàng

Rust cũng thay đổi thói quen lập trình ở các ngôn ngữ khác

  • Trình biên dịch Rust liên tục phàn nàn về cả những đoạn code mà trong ngôn ngữ khác người ta xem là bình thường, và qua quá trình đó thói quen viết code của tác giả đã thay đổi
  • Giờ đây, ngay cả trong ngôn ngữ khác, tác giả cũng cảm thấy khó chịu nếu thứ tự các dòng code có gì đó không ổn hoặc nếu giá trị trả về không được kiểm tra
  • Ngay cả khi gặp lỗi runtime, tác giả cũng cảm thấy phản ứng bài xích mạnh hơn trước rất nhiều
  • Sự nghiêm ngặt của Rust gây khó chịu, nhưng một khi đã quen với cảm giác được compiler bảo vệ thì rất khó quay lại ngôn ngữ khác

Clippy hữu ích hơn cả một linter thông thường

  • Clippy là linter của Rust, nhưng nó gần với một công cụ hỗ trợ thân thiện chuyên gợi ý mã thay thế hơn là chỉ đơn thuần kiểm tra lỗi
  • Thư viện chuẩn của Rust rất lớn, và vì các tính năng bị phân tán trên nhiều type, trait, macro và function nên việc tìm đúng API cần dùng không hề dễ
  • Nhiều rule có thể phát hiện các pattern phổ biến vốn nên được thay thế bằng method hoặc type tốt hơn từ thư viện chuẩn
  • Hàng trăm rule bao phủ hiệu năng, độ dễ đọc và các lớp gián tiếp không cần thiết, đồng thời cung cấp mã thay thế khi có thể
  • Cấu hình lint ở phạm vi toàn dự án có vẻ sẽ khả thi thông qua Cargo issue, nhưng trước lúc đó Wick đã phải dùng script để tự động cập nhật cấu hình lint inline cho hàng chục crate

Hệ sinh thái có những khoảng trống phải chấp nhận

  • Vấn đề cấu hình Clippy toàn cục chỉ là một ví dụ về khoảng trống trong hệ sinh thái thường gặp ở tool và library của Rust
  • Các issue liên quan hiện đã được đóng, nhưng chúng từng mở suốt nhiều năm và mất rất lâu mới được giải quyết
  • Rust đã thu hút người dùng mới đủ mạnh để nhiều năm liền được chọn là “ngôn ngữ được yêu thích nhất”, nhưng dòng chảy đó không tự động chuyển hóa thành sự cải thiện mạnh mẽ ngay lập tức ở library và tool
  • Thường xuyên xuất hiện các fork dùng một lần để xử lý các use case cụ thể, và ở Wick tác giả cũng gặp tình huống tương tự khi định gửi PR
  • Những lý do khả dĩ gồm áp lực phải duy trì API ổn định và hệ thống kiểu quá chi tiết
    • Chủ sở hữu library khó chấp nhận ngay cả thay đổi nhỏ vì chúng có thể dẫn tới major version bump
    • Gánh nặng viết code Rust đáp ứng được nhu cầu của tất cả mọi người cũng rất lớn

Cargo, crates.io và ma sát khi phát hành workspace

  • Cấu trúc repository của Wick được thiết kế dựa trên các dự án phổ biến và ban đầu trông khá hợp lý, nhưng vấn đề xuất hiện ở giai đoạn phát hành
  • Dùng Cargo để build, test và sử dụng các crate ở cấp độ module thì dễ, nhưng phát hành lên crates.io lại là câu chuyện khác
  • Trên crates.io, muốn publish package thì mọi crate được tham chiếu đều phải được phát hành riêng lẻ
    • Việc không cho phát hành crate phụ thuộc vào package chỉ tồn tại trong filesystem cục bộ là hợp lý
    • Nhưng với cấu trúc tự nhiên của dự án lớn được chia thành các module nội bộ nhỏ, bạn không thể publish kèm các sub-crate chỉ tồn tại bên trong crate cha
  • Sau đó có đính chính rằng ngay cả crate có local dev dependency vẫn có thể publish nếu Cargo.toml không chứa version
  • Bản thân hỗ trợ Cargo workspace là rất tốt, và trải nghiệm quản lý dự án lớn nhìn chung vẫn tốt hơn đa số ngôn ngữ khác
  • Tuy nhiên, workspace không giải quyết được bài toán phát hành, và dù có nhiều cách cấu hình thì cũng khó tìm được một “đáp án đúng” có thể phát hành trơn tru
  • Việc tồn tại rất nhiều utility crate liên quan đến cargo workspace publish tự nó đã cho thấy vấn đề
  • Mỗi lần publish Wick, tác giả thường mất hơn 1 giờ để ghép các thao tác lặp thủ công với những công cụ chỉ hoạt động một phần

async là một trong những nguồn ma sát lớn nhất

  • async của Rust tạo cảm giác như một tính năng được thêm vào sau khi ngôn ngữ đã hình thành, và trong quá trình sử dụng thực tế nó cũng thường gây cản trở như một tính năng gắn thêm về sau
  • Việc hiểu và giải quyết lỗi rất khó, và ngay cả khi tìm cách xử lý thì cũng phải lọc kết quả theo nhiều runtime khác nhau cùng các cách làm async riêng của chúng
  • Có những thư viện async có thể không dùng được bên ngoài một async runtime cụ thể
  • Với góc nhìn của người đã dùng JavaScript 20 năm và có cả kinh nghiệm Go, async của Rust là nguồn gây thất vọng và ma sát lớn nhất
  • Dù không phải vấn đề không thể vượt qua, nhưng luôn phải chuẩn bị tinh thần rằng sự cố async có thể xuất hiện bất cứ lúc nào
  • Ở ngôn ngữ khác, async thường vận hành tự nhiên đến mức gần như vô hình

Refactor có thể trở thành công việc nặng nhọc

  • Hệ thống kiểu phong phú của Rust vừa là ưu điểm vừa là nhược điểm
  • Tư duy bằng kiểu của Rust là điều tuyệt vời, nhưng quản lý các kiểu của Rust có thể là cơn ác mộng
  • Kiểu generic, lifetime generic và trait constraint có thể xuất hiện trong dữ liệu và function signature
  • Bản thân constraint cũng có thể lại mang generic type và lifetime, đến mức đôi khi type constraint còn nhiều hơn cả code thực tế
  • Mỗi impl đều phải định nghĩa generic, nên lúc viết ban đầu đã phiền phức, còn khi refactor thì thay đổi nhỏ có thể kéo theo một chuỗi chỉnh sửa lớn
  • Khi phải lặp lại cùng một danh sách constraint hay generic ở nhiều nơi, ngôn ngữ và công cụ hiện chưa có cách alias hoặc tham chiếu tới định nghĩa trung tâm để giảm gánh nặng trùng lặp này

Đánh giá cuối cùng: mạnh mẽ nhưng tốn kém

  • Rust đủ đa dụng để có thể viết code cấp hệ thống, ứng dụng CLI, web server và web client bằng cùng một ngôn ngữ
  • Với WebAssembly, có thể chạy LLM bằng cùng một binary trên trình duyệt và trên command line
  • Chương trình Rust có thể rất vững chắc, và một khi đã cảm nhận rõ những vấn đề mà Rust ngăn chặn được thì rất khó quay lại ngôn ngữ khác
  • Khi tạm quay về Go, tốc độ phát triển lại trở nên hấp dẫn, nhưng sau khi gặp runtime panic thì ưu điểm đó cũng bị lung lay
  • Rust rõ ràng có những nhược điểm
    • Khó tuyển dụng
    • Học chậm
    • Quá cứng cho việc lặp nhanh
    • Đặc biệt khó truy vết vấn đề về bộ nhớ và hiệu năng trong async code
    • Không phải mọi library đều đủ tốt cho code an toàn
    • Tooling vẫn còn nhiều dư địa để cải thiện
  • Dù nhóm nhỏ đã làm được những điều đáng kinh ngạc, cũng có nhiều chướng ngại lớn, và vì Wick có những lý do kỹ thuật riêng khiến Rust phù hợp hơn, tác giả cho rằng vẫn còn quá sớm để kết luận Rust có thật sự đáng giá với Wick hay không
  • Nếu cần lặp nhanh, rất có thể Rust không phải lựa chọn phù hợp
  • Nếu phạm vi đã rõ hoặc có thể chấp nhận chi phí ban đầu lớn hơn, Rust là lựa chọn đáng được cân nhắc nghiêm túc
  • Khi góc nhìn WebAssembly ngày càng mạnh lên theo từng tháng, khả năng tái sử dụng phần mềm vững chắc đã viết một lần ở nhiều nơi đang ngày càng trở nên thực tế hơn

1 bình luận

 
GN⁺ 2023-10-26
Ý kiến trên Hacker News
  • Tôi đã dùng Rust rất nhiều, nhưng sau vài năm vẫn cảm thấy năng suất thấp
    Gần đây tôi dùng Zig nhiều hơn; có thể chỉ tập trung vào phần code mình muốn viết, không phải bận tâm nên dùng công cụ hay thư viện nào, nên cảm giác năng suất cao hơn khoảng 10 lần
    Tôi biết Rust mang lại an toàn bộ nhớ và điều đó quan trọng, nhưng tính dễ dùng của nó thật sự tệ. Mỗi lần dùng Rust tôi đều có cảm giác bị ràng buộc, luôn phải đi tìm thư viện hoặc tra cách làm, nên không thể cứ thế “gõ code” được
    Hệ thống kiểu cũng có thể phình ra ngoài tầm kiểm soát, và nhiều khi khó biết trên một struct nào đó thực sự có thể gọi những phương thức nào. Rust là một công cụ tuyệt vời và giải quyết được nhiều vấn đề, nhưng tôi không nghĩ nó là một ngôn ngữ đa dụng tốt

    • Cảm giác đó không phải ai cũng có. Với bất kỳ việc gì phức tạp hơn script shell một chút, tôi dùng Rust, và cả window manager của tôi cũng được điều khiển bằng một chương trình Rust
      Với tư cách người đã dùng Python gần 20 năm, tôi có thể nói rằng hiện tôi làm việc trong Rust cũng nhanh như trong Python
    • Đồng ý. Ở thời điểm hiện tại, tôi cảm thấy mình có năng suất cao hơn nhiều trong C và C++ so với Rust
      Theo tiêu chí của tôi, Rust dường như đã bỏ lỡ hoàn toàn điểm cân bằng phù hợp. Nó quá khắt khe với các chi tiết cấp thấp để viết ứng dụng cấp cao, nhưng lại quá phức tạp để viết embedded hay hệ điều hành
      Với vế trước, tôi sẽ chọn C++, Java, Haskell, OCaml, thậm chí Go pha thêm một chút C; còn với vế sau, C dùng như macro assembly phù hợp hơn nhiều
      Tôi vẫn có cảm giác rằng ý tưởng ban đầu của Graydon Hoare — hướng OCaml/SML với linear types, garbage collection, stack allocation, green threads và CPS — lẽ ra đã trở thành một ngôn ngữ tốt hơn nhiều
    • Trải nghiệm của tôi thì hoàn toàn ngược lại. Khi dùng Rust cho hệ thống nhúng, sự tự tin và tốc độ của tôi tăng lên rất nhiều
      Trong C, chỉ một lỗi nhỏ cũng thường dẫn đến undefined behavior và đủ thứ rắc rối, nhưng Rust không có chuyện đó, nên nó thực sự thay đổi cuộc chơi
    • Tôi tò mò bạn viết loại code nào. Rất cấp thấp, hay rất cấp cao?
      Ý bạn là trong Zig không cần tìm thư viện hay không cần tìm hiểu phải làm thế nào à?
    • Tôi đồng ý với cảm giác tổng thể, nhưng diễn đạt thành lời thì khó
      Rust buộc bạn phải quyết định trước từng bit và byte sẽ đi đâu, được dùng ở thread nào, và sẽ được xử lý theo kiểu thay đổi nào. Nếu không phải ở mức parser hay microcontroller thì quá trình này tạo cảm giác nhàm chán
      Tôi thích cách làm cho thứ gì đó chạy được trước rồi mới quyết định cấu trúc API tốt nhất, nhưng Rust xung đột với quá trình đó
      Dù hệ thống kiểu của Rust mạnh hơn, với Swift bạn vẫn có thể đạt 90% hiệu năng và mọi thứ trôi chảy tự nhiên hơn nhiều
  • Việc crates.io không có namespace có lẽ là điểm đáng phê bình nhất
    Bất kỳ ai cũng có thể chiếm trước các tên gói phổ biến trong phạm vi toàn cục, và trừ khi tránh kho crates.io, hầu hết mọi người phải chấp nhận điều đó. Nhưng một số gói mang các tên phổ biến đã bị chiếm trước ấy thực ra không phải là gói tốt nhất để dùng
    Có thể đây là phản ứng ngược với kiểu ký hiệu DNS đảo ngược của Java vì nó dài dòng và phiền phức, nhưng cách như GitHub — đặt namespace người dùng/nhóm trước tên gói — có lẽ đã là một điểm trung dung tốt

    • Tôi từng làm một phân tích để tìm người chiếm tên trên crates.io, và tính ra người chiếm tên hàng đầu tạo crate với tốc độ khoảng một crate mỗi 30 giây, suốt cả tuần
      Tôi đã gửi phân tích đó cho nhóm crates.io và cũng chỉ ra rằng có chính sách cấm tự động hóa, nhưng họ trả lời rằng như vậy chưa đủ bằng chứng về việc chiếm tên
      Vấn đề của crates.io là ngay cả khi có chính sách rõ ràng, họ cũng không thực thi. Vì thế các tên crate ngắn, dễ nhớ đã bị lấy hết, và cũng không có cách nào lấy lại
    • Có vẻ không hẳn là phản ứng ngược với DNS đảo ngược kiểu Java, mà gần hơn với việc đi theo quy ước mà đa số package manager thời đó đặt ra
      NPM, PyPI, RubyGems, Hex của Elixir, Cabal của Haskell, v.v.; quanh thời điểm Rust xuất hiện vào năm 2014–2015, tôi khó nghĩ ra package manager không thuộc Java nào không dùng một namespace toàn cục duy nhất
      Một số hệ sinh thái sau đó đã cố sửa chuyện này, nhưng hồi đó đơn giản là package manager thường hoạt động như vậy
    • Maven và Java không được đánh giá đủ cao ở khía cạnh quản lý dependency hoạt động tốt
      Các hệ thống quản lý dependency kém hơn của những ngôn ngữ ra đời sau hầu như không học được gì từ ví dụ đi trước
    • Cách dùng URL cho package khá hợp lý. Nó hoạt động tốt trong hệ sinh thái Go
      Ưu điểm là ở cấp ngôn ngữ không còn cần một cơ sở dữ liệu package toàn cục. Nếu bạn đưa package lên example.com/your-thing thì coi như nó được release ngay
      Tất nhiên, nếu muốn thì vẫn có thể cung cấp cache và công cụ tìm kiếm riêng
    • Tôi không dùng Rust thường xuyên, nhưng kiểu vấn đề này thật sự rất phiền trong toàn bộ các kho package
      Kiểu như http-server thì tệ nên đừng dùng, phải dùng MuffinTop, và bạn cứ phải tự biết điều đó
      Khái niệm tên package được chứng nhận khá thú vị, nhưng nếu theo thời gian code phía sau alias thay đổi thì trên thực tế có thể rất dễ gây nhầm lẫn
      Rốt cuộc, có lẽ nó vẫn sẽ là một phần của quá trình trở thành chuyên gia miền trong bất kỳ hệ sinh thái nào
  • Nếu tạo .cargo/config.toml ở root của workspace, nó sẽ áp dụng cho mọi crate, nên có thể thiết lập lint Clippy toàn cục
    Trong file đó, chỉ cần đặt dưới [build] dạng rustflags = ["-Wclippy::lint_name_to_warn", "-Dclippy::lint_name_to_deny"]
    Tuy nhiên, rustflags không được cộng dồn mà bị ghi đè, nên nếu có nguồn khác như biến môi trường RUSTFLAGS thì nó sẽ ghi đè cấu hình này

    • Chúng tôi chạy một script khi commit để thêm lint vào các file lib.rs hoặc main.rs. Đơn giản thôi
  • Tôi đang học Rust vì rõ ràng nó sẽ trở nên quan trọng về mặt nghề nghiệp. Tôi thật sự muốn thích nó và cũng thấy các ưu điểm, nhưng trong số những ngôn ngữ tôi từng dùng đến nay, nó thuộc nhóm khó chịu nhất
    Tôi vẫn luôn hy vọng rằng khi thành thạo hơn thì cảm giác ghét sẽ biến mất, nhưng càng leo lên đường cong học tập thì tôi cũng không thấy có cảm tình hơn mấy
    Không sao. Chắc đây không phải là ngôn ngữ duy nhất mà tôi vừa dùng thành thạo vừa không thích. Chỉ là có quá nhiều người nói rằng họ yêu Rust nên tôi đã nghĩ mình cũng sẽ thích nó

    • Như một phản ví dụ, tôi thích lập trình Rust. Thời kỳ vật lộn với borrow checker đã qua từ lâu, và dạo này lỗi cũng hiếm, trừ khi tôi cố tình gây ra để kiểm tra kiểu
      Tôi cũng thấy trình biên dịch Rust đã tốt hơn trong việc chấp nhận nhiều trường hợp hợp lệ hơn
      Mấu chốt để hiểu borrow checker là hiểu mô hình bộ nhớ nền tảng. Mô hình bộ nhớ của Rust giống C, ngoại trừ các phần mở rộng cho những trừu tượng như generic
      Các quy tắc của borrow checker ban đầu trông có vẻ tùy ý, nhưng chúng gắn chặt với mô hình bộ nhớ này. Giá trị thật sự là khi bạn vô tình bị borrow checker chặn lại, vì đó là lỗi do mất tập trung tạo ra
      Điều đáng sợ là với các ngôn ngữ như C hay C++, đoạn mã như vậy có thể cứ được chấp nhận và chạy tiếp
      Hệ thống kiểu nghiêm ngặt và borrow checker của Rust nhẹ nhàng thúc bạn cấu trúc mã đúng cách, và tôi tin chắc chúng đã cải thiện thiết kế mã của tôi trong mọi ngôn ngữ tôi dùng
    • Tôi đã thử học Rust nhiều lần nhưng lần nào cũng bị bật ra. Cảm giác đơn giản là khả dụng không tốt
    • Điều vẫn làm tôi vướng là không có giá trị mặc định/tham số hàm có tên
      Đây là một tính năng rất cơ bản về tính tiện dụng cho lập trình viên mà hầu hết các ngôn ngữ phổ biến đều có, và ngay cả C++ cũng đã có tham số mặc định từ lâu
    • Tôi tò mò bạn không thích điều gì
  • Tôi là cựu lập trình viên C/C++ từng gọi pthread gần như hằng tuần trong 10 năm, còn giờ thì dùng Rust bất đồng bộ ở khắp nơi
    Tôi không hiểu vì sao async lại bị ghét đến vậy. Theo tôi, mọi người nên dùng async cho mọi thứ. Kể cả những tác vụ “đơn giản” nhìn bề ngoài như đơn luồng

    • Tôi nghĩ vấn đề là tính lây lan của nó. Đặc biệt trong môi trường embedded hoặc Wasm, async chủ đạo có thể không phải loại async tôi muốn
      Nếu trường hợp sử dụng của tác giả là Wasm thì chắc chắn sẽ có góc nhìn khác
      Các công việc dùng bộ đệm lớn hoặc tái sử dụng bộ đệm để tránh chi phí cấp phát cũng thường hưởng lợi từ thread pool kiểu cũ. Có thể giải quyết phần nào bằng bump allocator hoặc shard allocator, nhưng khi bị ràng buộc CPU trong các vòng lặp chặt có thể vector hóa, thread pool hoạt động tốt hơn
      Async là một công cụ tốt, nhưng không phải ngữ cảnh nào nó cũng là tối ưu
    • Gần đây tôi có dùng thử một chút Rust bất đồng bộ để so sánh cách xử lý lỗi trong các lời gọi async lồng nhau khác Go ra sao, nhưng ngay cả ví dụ vụn vặt tôi định viết cũng có vẻ không làm được nếu không có Tokio
      Go, C#, TypeScript không có rào cản như vậy
      Ví dụ, có vẻ như nếu không có decorator lấy từ Tokio thì trong hàm main cũng không await được
    • Vấn đề hiện tôi gặp là một số thứ như rhai Engine không phải Send, nhưng tôi lại muốn dùng nó bên trong closure bất đồng bộ
      GPT-4 đề xuất tạo tokio Runtime trong một thread rồi dùng block_on(). Tôi định thử vào ngày mai. Đây là dự án Rust nghiêm túc đầu tiên của tôi
    • Tôi lại học được điều ngược lại. Các lập trình viên có xu hướng biến mọi thứ thành async một cách không cần thiết dù chẳng cần, làm tăng độ phức tạp và gánh nặng tinh thần
    • Bạn có thể giải thích chi tiết hơn không? Tôi tò mò ví dụ nào trong các tác vụ đơn giản nhìn bề ngoài như đơn luồng lại đáng chọn async
      Tôi cũng tò mò liệu câu “giờ tôi dùng Rust bất đồng bộ ở khắp nơi” có nghĩa là “dùng Tokio ở khắp nơi” không
  • Lập trình Rust thật sự không giống một mối quan hệ mang tính ngược đãi. Trình biên dịch cố gắng giúp bạn hết mức, đặc biệt thông báo lỗi của rustc thuộc hàng tốt nhất thế giới

    • Hệ điều hành muốn lập trình viên xử lý tài nguyên đúng cách, và trình biên dịch Rust khiến việc đó trở nên rất dễ
      Thứ giống một mối quan hệ ngược đãi hơn không phải trình biên dịch Rust mà là phía hệ điều hành. Phần cứng cũng vậy, vì nó phải thực thi assembly đúng cách; nhìn theo kiểu đó thì cũng có thể gọi là một mối quan hệ ngược đãi
      Thông báo lỗi của Rust là độc nhất, không trình biên dịch nào tiệm cận được
      Nhân tiện, gần đây tôi dùng LaTeX và thông báo lỗi của nó thật kinh khủng. Việc nhìn vào lỗi để tìm xem sai ở đâu đúng là ác mộng
    • Nhìn chung là tốt, nhưng tôi thật sự ghét việc bất kỳ lỗi nào trong hàm bất đồng bộ cũng tạo ra lỗi rằng Future không còn là SendSync nữa tại mọi điểm gọi đệ quy
      Cả console bị phủ kín bởi lỗi, còn lỗi cú pháp thật sự thì bị chôn đâu đó ở giữa
    • Trình biên dịch Rust là trình biên dịch đầu tiên tôi thấy dùng từ “perhaps”
      Phàn nàn chính của tôi là đến giờ vẫn chưa hiểu hoàn toàn lifetime, và trình biên dịch cũng không thể lần nào cũng giúp được. Tôi hiểu điều đó, vì trình biên dịch phải phán đoán một cách bảo thủ
  • Trong kiểm thử C++, người ta thường nói “nếu biên dịch được thì phần lớn là đúng”
    Nếu bạn nghĩ Rust xử lý quá nhiều lỗi khiến các trường hợp kiểm thử phổ biến trở nên vô nghĩa, thì đó là dấu hiệu cho thấy ở các ngôn ngữ khác bạn đã không kiểm thử đúng thứ cần kiểm thử
    Thứ cần kiểm thử không phải là vấn đề của bản thân ngôn ngữ, mà là logic nghiệp vụ
    Nếu nhìn vào mã kiểm thử và cảm thấy “nếu là JavaScript thì sẽ kiểm thử, nhưng với Rust thì không cần”, thì cứ xóa bài kiểm thử đó đi là được

    • Tôi đã nghe câu “nếu biên dịch được thì phần lớn là đúng” với Haskell và Rust, nhưng chưa từng nghe ai áp dụng nó cho C++
    • Ngoại lệ con trỏ null là lỗi phá vỡ logic nghiệp vụ
      Không có sự phân biệt “logic nghiệp vụ so với vấn đề ngôn ngữ”. Vì ngôn ngữ là nền tảng nơi logic nghiệp vụ được đặt lên
      Nếu không kiểm thử các cách thất bại, thì tôi không biết kiểm thử còn có ý nghĩa gì
    • Có một kiểu lập trình viên C++ coi mã rác hỏng bét đã biên dịch được thì chắc là đúng, và điều đó có gì đó kỳ lạ đến đáng nể
      Khác với hầu hết các ngôn ngữ quen thuộc, C++ có IFNDR, thường bị đùa là “dương tính giả” cho câu hỏi “đây có phải là chương trình C++ không?”
      Trình biên dịch C++ tuân thủ chuẩn bị cấm cảnh báo trong một số trường hợp khi mã bạn viết có vẻ vô nghĩa, và phải cứ tiếp tục rồi xuất ra thứ gì đó
      Đó có thể là một tệp thực thi chạy được, hoặc cũng có thể là một tệp thực thi gây thảm họa vào mỗi thứ Sáu. Không có cách nào biết được
      Chuẩn ISO có xác định các trường hợp như vậy, nhưng quá mơ hồ nên khó nắm chính xác những gì được bao gồm; tôi đoán phần lớn phần mềm C++ không tầm thường ngày nay trên thực tế đều là IFNDR. Tốt hơn là cứ bác bỏ cả ngôn ngữ này
  • Rust dường như cuối cùng đã phá vỡ ý tưởng rằng lập trình viên phải hoàn toàn kiểm soát và ý thức được mọi việc trình biên dịch làm
    Thật ra từ nhiều thập kỷ trước điều đó đã không còn đúng, và trình biên dịch gần như là ma thuật. Rust, thông qua borrowing, đã đảo ngược đáng kể dòng chảy đó, khiến mọi người thoải mái hơn với việc trình biên dịch biết rõ hơn họ
    Tôi mong chúng ta còn thoải mái hơn nữa. Nếu không cần thiết về mặt thuật toán, ta không nên phải lặp tường minh qua collection từ đầu. Nhiều thao tác nên được song song hóa ngầm định. Ước gì có một Bash kiểu Rust

    • Trải nghiệm của tôi với Rust thì hoàn toàn ngược lại
      Rust khá minh bạch về những gì nó làm, và rất bảo thủ với ma thuật trình biên dịch. Ngôn ngữ này không cấp phát heap, không reference counting, cũng không có chuyển đổi kiểu số ngầm định
      Những kiểu không được khai báo là có thể sao chép ngầm định thì sẽ không bị sao chép; và ngay cả khi có, điều đó chỉ hợp lệ với các kiểu có thể sao chép bằng một phép memcpy nông đơn giản
      Rust dùng zero-cost abstraction ở khắp nơi, nên có thể dự đoán mã sẽ được biên dịch thành gì, và thường là đơn giản. Bố cục mặc định của các kiểu chuẩn cũng được biết rõ, nên ta biết việc duyệt Vec sẽ được biên dịch thành một vòng lặp tăng con trỏ, và không có tính song song ngầm định
      Mô tả borrowing là “trình biên dịch biết rõ hơn lập trình viên” nghe rất lạ. Borrowing giống kiểm tra kiểu hơn. Nếu bạn khai báo một kiểu nào đó là tạm thời rồi lại dùng như thể nó sống lâu, lỗi sẽ xuất hiện
      Cũng giống như khi một hàm khai báo trả về struct Foo nhưng lại trả về Bar thì sẽ lỗi. Lý do trình biên dịch “biết rõ hơn” chỉ là vì bạn đã viết bug
      Borrowing cũng được biên dịch thành việc dùng con trỏ trực tiếp mà không cần garbage collection, và với struct cùng hàm C ABI thì được đảm bảo đúng nghĩa là giống hệt con trỏ C. Nếu nghĩ mình biết rõ hơn trình biên dịch, bạn cũng có thể dùng unsafe để né lifetime
    • Đã có crate cung cấp parallel iterator. Chỉ cần đổi tên lời gọi iter() là được
      Tuy vậy, tôi không đồng ý rằng điều đó nên là ngầm định
    • Cách nói “không kiểm soát được những gì trình biên dịch làm” hơi phóng đại
      Ở một số mặt, Rust trao cho lập trình viên nhiều quyền kiểm soát hơn C. Ví dụ Rust hỗ trợ inline assembly trong chuẩn, còn inline assembly của C thì phụ thuộc vào extension theo từng nhà cung cấp
      Tuy nhiên các giá trị mặc định tiện lợi thì rất khác. Trong Rust, ép kiểu không an toàn đòi hỏi nhiều thủ tục và sự cẩn trọng, và phải tuân theo nhiều quy tắc hơn C
      Đặc biệt, hầu như mọi reference của Rust đều hoạt động như restrict, và khi unsafe cast từ raw pointer sang safe reference thì rất dễ phá hỏng điều này. Vì vậy nếu có lựa chọn, sẽ có động lực rất mạnh để không viết kiểu mã đó
    • Với tư cách là người dùng rất nhiều unsafe Rust, bao gồm cả những thứ như tagged pointer, tôi hoàn toàn không đồng ý
      Ngược lại, tôi muốn có sự kiểm soát tường minh hơn, và muốn giảm gánh nặng đó bằng một hệ thống kiểu biểu đạt mạnh hơn. Lý tưởng nhất là hệ thống kiểu của Rust trở thành một biến thể kiểu Prolog
    • “Bash kiểu Rust” có lẽ sẽ là Bash có kiểu, đặc biệt là số dấu phẩy động, ít tình huống ngoại lệ, có hàm với tham số tường minh và các flag dòng lệnh đơn giản
  • Học khi nào nên đầu tư vào ràng buộc kiểu và khi nào không là một bài học quan trọng
    Đây không phải vấn đề riêng của Rust, nhưng cách nó biểu hiện có thể hơi khác
    Tôi đã từng xử lý C++ bị kiểu hóa quá mức và Java bị trừu tượng hóa, kiểu hóa quá đà; cả hai đều gặp cùng một loại vấn đề khi refactor
    Ngược lại, tôi cũng đã thấy nhiều Go thiếu kiểu và thiếu tài liệu, nơi một số giá trị cụ thể bị rải rác khắp nơi và trở thành mìn runtime, khiến việc refactor có thể trở nên thật sự cực kỳ khó
    Cảm giác tiến triển ban đầu đến nhanh hơn, nhưng thường thì bạn sẽ phát hành bug cho người dùng
    Không có đáp án kỳ diệu nào cho sự đánh đổi này. Rust là ngôn ngữ cung cấp phạm vi lựa chọn khá rộng trên trục này

  • Câu “Rust la hét suốt cả ngày, mỗi ngày, về những thứ mà trong cuộc sống trước đây ta từng xem là hoàn toàn bình thường” thì một trình biên dịch C tốt cũng làm tương tự nếu bật hết mọi flag
    Tôi thích những ngôn ngữ và trình biên dịch cho phép tùy chọn tắt tiếng la hét đó và cố ý viết code tệ. Code tệ nhưng chạy được và có thể viết nhanh thường tốt hơn code hoàn hảo nhưng mất mãi mới xong
    Có thể tạo một proof of concept tệ nhưng hoạt động được, rồi sau đó sửa cho đỡ tệ hơn
    Việc “thu hút người dùng mới khá tốt nhưng thư viện hay công cụ không cải thiện mạnh, chỉ sinh ra các fork dùng một lần để xử lý những use case cụ thể” không liên quan đến tuổi đời
    Thu hút core developer là việc khó, và cần rất nhiều nỗ lực để khiến nó hấp dẫn. Hơn nữa, các chuẩn mực văn hóa do những người dùng sớm định hình; việc không có chuẩn mực đôi khi cũng gây hại chẳng kém chuẩn mực xấu
    Nhìn vào Python, cách tiếp cận lỏng lẻo với môi trường phát triển và thực thi đã dẫn đến khoảng 50 cách cạnh tranh nhau để phát triển hoặc chạy chương trình Python
    Kho gói được dùng nhiều nhất, PyPI, đã là một mớ hỗn độn suốt nhiều năm; ít người xây tiếp trên các gói có sẵn, tên thì trông như do bộ sinh từ ngẫu nhiên tạo ra, hệ sinh thái có nhiều mã độc, và thậm chí còn không thể tìm kiếm gói từ dòng lệnh
    Đây không phải lỗi của ngôn ngữ, mà là lỗi của cộng đồng và core team đã đứng ngoài như người quan sát. Văn hóa quan trọng hơn cả công nghệ ở trung tâm của nó
    Tôi không có ý chỉ trích riêng Python; chỉ là tôi hiểu rõ vấn đề đó hơn. C đã tồn tại nửa thế kỷ, nhưng cộng đồng của nó cũng chưa hệ thống hóa được tử tế dù chỉ một nửa các giải pháp mà những ngôn ngữ hiện đại hơn đã đưa ra

    • Rust cũng hỗ trợ điều đó. Chỉ cần đánh dấu toàn bộ là unsafe
    • Không đời nào Linux và Windows bắt đầu port các component sang Rust chỉ vì “không có thư viện cải thiện”
    • Nói mạnh dạn thì nó khá giống TypeScript
      Ý là trong những tình huống trình biên dịch cứ la lên bắt sửa, nhưng bạn chỉ muốn thử ý tưởng trước khi trau chuốt cho hoàn hảo