- Dự án tmux-rs là công việc port toàn bộ mã nguồn tmux được viết bằng C sang Rust trong khoảng 6 tháng
- Ban đầu đã thử chuyển đổi tự động bằng công cụ C2Rust, nhưng do kết quả có khả năng bảo trì thấp nên cuối cùng đã chuyển sang chuyển đổi thủ công
- Trong quá trình build và liên kết giữa Rust-C, tác giả đã gặp nhiều bug và vấn đề cấu trúc
- Có những vấn đề và cách giải quyết riêng khi chuyển các mẫu đặc trưng của C như câu lệnh goto, cấu trúc dữ liệu bằng macro, trình phân tích yacc sang Rust
- Dự án hiện vẫn dựa trên unsafe Rust, nhưng hướng tới mục tiêu hoàn tất việc chuyển toàn bộ sang Rust thông qua biên dịch và chạy thử trước
Tổng quan dự án
- tmux-rs là dự án port toàn bộ codebase của tmux (khoảng 67.000 dòng mã C) sang Rust (khoảng 81.000 dòng, không tính chú thích và dòng trống)
- Tác giả thực hiện công việc này như một dự án cá nhân, đồng thời trải qua nhiều thử nghiệm, sai sót và học hỏi trong quá trình chuyển từ C sang Rust
Việc sử dụng C2Rust và giới hạn của nó
- Ban đầu, tác giả định dùng công cụ chuyển đổi tự động C2Rust để chuyển mã C của tmux sang Rust
- Mã được chuyển đổi tự động khó đọc, lớn hơn hơn 3 lần so với mã C gốc, và do nhiều ép kiểu không cần thiết, mất tên hằng số cùng nhiều vấn đề khác nên khả năng bảo trì giảm mạnh
- Trong quá trình refactor thủ công, tác giả cuối cùng đã loại bỏ kết quả từ C2Rust và chuyển sang cách trực tiếp viết lại bằng Rust dựa trên mã C làm tham chiếu
- Việc có thể build và chạy ngay từ giai đoạn đầu nhờ C2Rust đã giúp xác nhận tính hợp lý và khả thi của dự án
Thiết kế quy trình build
- tmux dựa trên hệ thống build autotools, và liên kết mã Rust với mã C hiện có dưới dạng thư viện tĩnh
- Ban đầu, thư viện Rust được link vào binary C, nhưng sau khi phần lớn mã đã được port sang Rust thì cấu trúc được đổi thành binary Rust link với thư viện C (
cc crate)
- Để tự động hóa build, tác giả viết script build.sh và
build.rs, cho phép kiểm tra build dần dần ngay cả trong lúc đang dịch mã
- Các vấn đề thường gặp trong quá trình build như thiếu khai báo header, không khớp chữ ký hàm được giải quyết từng bước theo đơn vị hàm
Các ví dụ bug gặp phải trong quá trình chuyển đổi
Bug 1: khai báo ngầm và trả về con trỏ
- Trong một hàm đã chuyển sang Rust, do phía C có khai báo ngầm cho kiểu trả về là con trỏ, 4 byte trên của giá trị trả về bị cắt mất và được truyền sai
- Cách giải quyết là thêm prototype hàm chính xác ở phía C để compiler hoạt động đúng
Bug 2: không khớp kiểu trường trong struct
- Trong struct client, do dịch sai kiểu trường (con trỏ so với số nguyên), phép tính offset bộ nhớ bị lệch, dẫn đến lỗi diễn giải dữ liệu và segfault
- Vấn đề được giải quyết bằng cách đồng bộ chính xác định nghĩa struct giữa C và Rust
Port các mẫu đặc trưng của C sang Rust
Sử dụng raw pointer
- Việc ánh xạ trực tiếp con trỏ C sang reference của Rust có thể vi phạm các quy tắc an toàn của Rust như cho phép null hay bảo đảm khởi tạo
- Vì vậy, hầu hết các cấu trúc con trỏ được chuyển sang dùng raw pointer (
*mut, *const) và chỉ sử dụng trong vùng không an toàn
Xử lý câu lệnh goto
- Trong C2Rust, luồng điều khiển của goto được chuyển đổi theo thuật toán, nhưng trong đa số trường hợp có thể triển khai đầy đủ bằng labeled block + break và labeled loop + continue của Rust
Port cấu trúc dữ liệu bằng macro
- tmux triển khai cây red-black xâm nhập và linked list bằng macro C
- Trong Rust, tác giả dùng generic trait và iterator tùy biến để tạo giao diện tương tự (vấn đề triển khai lặp của một trait đơn được xử lý bằng kiểu dummy)
Chuyển đổi parser yacc
- tmux dùng yacc(lex) cho parser của tệp cấu hình
- Trong Rust, tác giả dùng crate lalrpop có cấu trúc tương tự để port nguyên văn ngữ pháp và action, đồng thời cũng viết adapter để liên kết với lexer C
- Trong quá trình này, tác giả cũng gặp giới hạn của lalrpop trong hỗ trợ raw pointer (như việc dùng
NonNull<T>)
Môi trường và công cụ phát triển
- Chủ yếu sử dụng neovim cùng các macro tùy chỉnh để thực hiện các thao tác chuyển đổi lặp đi lặp lại
- Ví dụ:
ptr == NULL → ptr.is_null() / ptr->field → (*ptr).field được ánh xạ thủ công
- Tác giả cũng thử công cụ tự động hóa (Cursor), nhưng vì có nhiều đoạn mã bị mất hoặc sai nên gánh nặng review code tăng lên
- Dù phần nào giúp giảm mỏi tay, hiệu quả về năng suất vẫn khá hạn chế
Kết luận và hướng đi tiếp theo
- Toàn bộ mã đã được port xong sang Rust và đã công bố phiên bản 0.0.1
- So với C2Rust, mã viết thủ công ở một số phần tốt hơn, nhưng hiện vẫn dựa trên unsafe Rust và còn nhiều bug
- Mục tiêu cuối cùng là chuyển sang safe Rust và hoàn tất việc port đầy đủ các tính năng của tmux sang Rust
- Tác giả mong muốn hợp tác và nhận phản hồi từ các nhà phát triển quan tâm tới Rust và tmux thông qua GitHub Discussions
4 bình luận
Ồ.. nhưng Rust có nhẹ hơn không?
Ồ... nghe hay đấy?
Trong số các plugin tmux, có cái như resurrect cũng ngốn khá nhiều bộ nhớ và còn hoạt động hơi kỳ nên tôi đã bỏ đi, nhưng tôi cũng tò mò không biết dùng cùng tmux-rs thì có khá hơn không.
https://rosettalens.com/s/ko/tmux-rs-intro
Ý kiến trên Hacker News
Đây thực sự là một trải nghiệm đầy cảm hứng khi đọc lại quá trình thực hiện dự án này. Tôi rất muốn bày tỏ sự ngưỡng mộ lớn đối với sự bền bỉ và lì lợm của tác giả. Câu “giống như làm vườn nhưng có nhiều segfault hơn” thật sự quá đồng cảm. Chính những dự án nghiêm túc kiểu sở thích như thế này thường là nơi người ta học được nhiều nhất.
Phần liên quan đến c2rust đặc biệt thú vị. Trước đây tôi cũng từng thấy những thay đổi tương tự do các công cụ chuyển đổi mã tự động giữa các ngôn ngữ mang lại. Những công cụ như vậy cực kỳ hữu ích để khởi tạo dự án nhanh và chứng minh tính khả thi, nhưng cuối cùng rất dễ tạo ra thứ mã trống rỗng, không mang đúng tinh thần của ngôn ngữ đích. Vì thế tôi nghĩ việc cuối cùng chuyển sang port thủ công, dù đau đớn, là một lựa chọn rất đúng. Tự động hóa có giới hạn trong việc diễn dịch ý đồ của mã C thành Rust an toàn và đúng chất Rust.
Ở mục “lỗi thú vị”, khi thấy câu chuyện #2 về việc sai khác
struct layout, tôi lập tức nhớ lại những cơn ác mộng với giao diện hàm ngoại lai (FFI) ngày xưa. Tôi cũng từng mất nguyên một tuần chỉ để bắt một lỗi hỏng dữ liệu rất tinh vi dostructpacking bị lệch giữa C++ và C#. Đó là kiểu lỗi khiến bạn nghi ngờ chính sự tỉnh táo của mình. Tìm ra được nó đòi hỏi sự kiên nhẫn debug đáng nể. Xin dành tràng pháo tay cho tác giả.Nhìn chung, tôi nghĩ dự án này là một ví dụ rất thực tế cho thấy mức độ khó và cả tiến trình hiện đại hóa mã hạ tầng cốt lõi. Mục tiêu lớn tiếp theo là chuyển từ
unsafesang safe Rust, và tôi thật sự rất tò mò họ sẽ chọn chiến lược nào.Tôi nghĩ việc chuyển toàn bộ
raw pointer,gotovà các luồng điều khiển phức tạp thành Rust an toàn, đúng chuẩn idiomatic mà không làm cả codebase sụp đổ, trên thực tế có thể còn khó hơn lần port đầu tiên. Tôi tò mò liệu họ có định đưa lifetime và borrow checker vào dần dần theo từng mô-đun hay không, và họ sẽ xử lý các cấu trúc dữ liệu intrusive như thế nào. Nếu thay bằng những thứ nhưBTreeMaptrong thư viện chuẩn thì có thể sẽ ảnh hưởng hiệu năng, mà biết đâu chính thiết kế intrusive ban đầu vốn đã nhắm tới điều đó.Dù sao thì đây vẫn là một công trình đáng kinh ngạc. Cảm ơn vì đã chia sẻ quá trình một cách chi tiết như vậy. Tôi sẽ tiếp tục theo dõi dự án trên GitHub
Tin này đúng kiểu kéo tôi vào luôn
Tôi đã tự phát triển một trình quản lý session tmux viết bằng Rust tên là rmuxinator trong vài năm nay, kiểu như bản clone của tmuxinator. Phần lớn mọi thứ hoạt động tốt, nhưng cuộc sống bận rộn nên tiến độ chậm, gần đây tôi mới quay lại chủ yếu để sửa lỗi. Tính năng tôi thêm gần nhất là cho phép dùng rmuxinator như một thư viện. Tôi muốn thử xem liệu có khả thi không nếu fork tmux-rs, thêm rmuxinator làm dependency, rồi khởi động session bằng file cấu hình theo từng dự án. Không phải tôi đang nói rmuxinator cần được upstream đưa vào, nhưng tôi vẫn nghĩ nếu những tính năng template session như thế này được tích hợp sẵn trong chính terminal multiplexer thì sẽ rất hữu ích.
Ngược lại, tôi cũng nghĩ có khi rmuxinator dùng tmux-rs như một thư viện để xử lý toàn bộ quản lý session mà không cần sinh shell command sẽ còn tốt hơn nữa (dĩ nhiên tôi vẫn chưa biết tmux-rs có hỗ trợ kiểu đó hay không)
Khi sửa xong đợt bug hiện tại, tôi nhất định sẽ thử ít nhất một trong những ý tưởng trên
Dù sao thì richardscollin làm rất tuyệt
Tôi rất thích thái độ kiểu “không có lý do đặc biệt nào để viết lại tmux bằng Rust cả, chỉ là một dự án sở thích thôi. Nó giống làm vườn nhưng có nhiều segfault hơn”
Không phải lúc nào tạo ra thứ gì mới cũng cần một đại nghĩa hay tính thực dụng rõ ràng. Đôi khi chính các dự án sở thích lại dẫn đến những khám phá bất ngờ. Bài viết chi tiết của tác giả thật đáng khâm phục
Nhân tiện, khu vườn của tôi thì đầy segfault. Với tôi, viết một dự án mới trong sân nhà còn có vẻ an toàn hơn
Đồng ý hết. Không phải mọi dự án đều cần tồn tại để thay đổi thế giới
Gần đây tôi đã từng viết lại fzf bằng Rust rs-fzf-clone
Không có lý do đặc biệt nào cả, fzf hiện tại vốn đã hoạt động rất tốt, mục tiêu chính chỉ là muốn tự mình trải nghiệm channel trong Rust và thuật toán tìm kiếm mờ. Đó là một quá trình học hỏi rất vui, và dù fzf gốc tốt hơn thì điều đó cũng không hẳn quan trọng. Mục đích là để thử cái mới và thực nghiệm
“Làm vườn là cái cớ tốt nhất để trở thành triết gia”
Khi ai đó gợi ý theo kiểu Rust đương nhiên vượt trội hơn C, tôi thường có phản ứng hoài nghi gần như theo bản năng. Nhưng tôi hay quên mất rằng mọi người cũng làm những dự án thế này chỉ vì vui thôi
Tôi thấy câu “chúng ta không nhất thiết chỉ cần lý do để tạo ra cái mới” rất ấn tượng
Nhưng tmux thì thật ra đâu có mới
Nó khiến tôi nghĩ xem liệu việc viết lại phần mềm hiện có sang một ngôn ngữ khác có nhất thiết cũng phải cần một lý do hay không
Câu “giống như làm vườn nhưng có nhiều segfault hơn” buồn cười thật. Tôi vẫn chưa quen với Rust nên khá tò mò không biết trong những tình huống cụ thể nào thì cần dùng
unsafeTôi thật sự ấn tượng với tinh thần của dự án này, và với việc phần lớn bình luận đều có bầu không khí tích cực
Thường thì người ta hay nói viết lại một ứng dụng trưởng thành bằng ngôn ngữ khác là ý tưởng không hay, nhưng thực tế là trong quá trình thử làm bạn sẽ học được rất nhiều. Quá trình mới thật sự quan trọng hơn kết quả
Với sự quan tâm mà dự án này đang nhận được và xu hướng phát triển của AI, tôi nghĩ đây có thể phát triển thành một dự án sở thích cực kỳ hấp dẫn cho người mới học Rust. Sửa từ những lỗi đơn giản, rồi thêm tính năng mới hoặc tối ưu hóa, đều là trải nghiệm rất lớn
Tôi muốn đề xuất một ý tưởng: biến Gemini CLI (hoặc LLM bạn thích) thành kiểu scratch buffer để nó tương tác với các cửa sổ/panel khác nhau trong một session tmux
Trong trường hợp của tôi, tôi thường chạy lệnh trên nhiều server qua các panel được đồng bộ và tự quản lý lỗi thủ công; nếu có thể giao việc thực thi lệnh cho AI, để nó phân tích output theo thời gian thực rồi thích nghi và tạo lại command, thì sẽ giống như một shell script tùy biến được sinh động theo ngữ cảnh
Ví dụ, tôi dùng gvim hằng ngày, nhưng nếu muốn làm một editor thì tôi không nhất thiết phải làm nó giống gvim, mà sẽ muốn sáng tạo ra một thứ mới chỉ có những tính năng tôi cần. Nếu đã đầu tư chừng này thời gian, tôi thấy thử làm thứ gì đó độc đáo theo hướng sáng tạo sẽ ý nghĩa hơn
Tôi vừa port tmux sang Fil-C trong chưa đầy 1 giờ (bao gồm cả port libevent và chạy test). Nó hoạt động rất tốt và mang lại trải nghiệm an toàn bộ nhớ hoàn toàn
Tôi thích những dự án như thế này. Tôi cũng đang nghĩ đến chuyện thử dấn sâu vào Rust
Nhân tiện, tôi muốn giới thiệu zellij (một terminal multiplexer viết bằng Rust)
Tôi chỉ là người dùng thôi, nhưng tôi luôn thích tìm kiếm rồi chuyển dần sang các giải pháp viết bằng Rust
Tôi vừa tình cờ xem video này: “Oxidise Your Command Line”
https://www.youtube.com/watch?v=rWMQ-g2QDsI
Một số phần có thể không cần thiết nếu bạn không phải lập trình viên Rust, nhưng cũng có khá nhiều mẹo hữu ích cho bất kỳ ai quen làm việc trong môi trường dòng lệnh
Tôi nghĩ trong c2rust hoàn toàn có thể có những cải tiến để giảm bớt mất mát thông tin mà tác giả đã chỉ ra, chẳng hạn như giữ lại tên hằng số. Vì gánh nặng của lần chuyển đổi đầu tiên là rất lớn
Nếu đến lúc các mô hình ngôn ngữ lớn có thể tự động chuyển toàn bộ mã C phức tạp sang Safe Rust một cách chính xác chỉ trong một giờ, thì những dự án như thế này sẽ trở thành ví dụ đại diện rất đậm chất tương lai
Tuy vậy, tác giả cũng nói rằng ở bước cuối họ đã thử với Cursor (tính đến giữa năm 2025) nhưng hiệu quả chuyển đổi giảm đi rõ rệt, nên tôi nghĩ hiệu năng thực tế vẫn còn khá xa mới tới đó
codemod.com và những nơi tương tự đã thử làm điều này dưới khái niệm “codemods” rồi
Codemods sử dụng AST (cây cú pháp trừu tượng) để cho phép chuyển đổi và refactor mã nhanh với quy mô lớn
Giới thiệu về refactoring API bằng codemods
Cụm “có thể hoàn hảo chuyển đổi mã C phức tạp sang Safe Rust trong một giờ bằng mô hình ngôn ngữ lớn” nghe rất cụ thể nên khá gây ấn tượng
Tôi mong code sau này sẽ còn gọn gàng hơn nữa. Tôi đã thử zellij nhiều lần, nhưng dù đã phát triển qua nhiều năm, nó vẫn còn thiếu vài tính năng mà tmux cung cấp rất thuận tay
Đặc biệt việc không thể ẩn/hiện status bar là điều gây khó chịu nhất
Xem issue #694 của zellij-org/zellij
Những key binding tôi hay dùng lại xung đột với binding mặc định của plugin quản lý session, khiến các chức năng chính như chọn thư mục bị chặn mất
Cuối cùng thành ra việc tạo session vẫn phải làm trực tiếp từ dòng lệnh thay vì qua plugin