Rust có đáng để đánh đổi đến vậy không?
(jsoverson.medium.com)- 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ó
nullgiú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
- Ví dụ: manual_is_ascii_check
- 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.tomlkhông chứaversion - 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ế
- Ví dụ: rxRust observable.rs
- 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- Ví dụ: Wasmtime Cranelift iter.rs
- 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
Ý 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
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
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
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
Ý 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 à?
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 đã 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
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
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
Ư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
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ụcTrong file đó, chỉ cần đặt dưới
[build]dạngrustflags = ["-Wclippy::lint_name_to_warn", "-Dclippy::lint_name_to_deny"]Tuy nhiên,
rustflagskhông được cộng dồn mà bị ghi đè, nên nếu có nguồn khác như biến môi trườngRUSTFLAGSthì nó sẽ ghi đè cấu hình nàylib.rshoặcmain.rs. Đơn giản thôiTô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ó
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
Đâ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 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
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
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
maincũng khôngawaitđượcEnginekhông phảiSend, 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 Runtimetrong một thread rồi dùngblock_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ôiTô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
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
Futurekhông còn làSendvàSyncnữa tại mọi điểm gọi đệ quyCả 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
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
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ì
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
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
memcpynông đơn giảnRust 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
Vecsẽ đượ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 địnhMô 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
Foonhưng lại trả vềBarthì sẽ lỗi. Lý do trình biên dịch “biết rõ hơn” chỉ là vì bạn đã viết bugBorrowing 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é lifetimeiter()là đượcTuy vậy, tôi không đồng ý rằng điều đó nên là ngầm định
Ở 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à khiunsafecast 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ã đó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
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
Ý 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