搜尋
首頁後端開發Golang發布 Viddy v.Migration 從 Go 到 Rust

介紹

在本文中,我想分享我在重新實作 Viddy(我一直在開發的 TUI 工具)過程中獲得的經驗和見解,從 Go 到 Rust v1.0.0 版本。 Viddy 最初是作為 watch 命令的現代版本開發的,但這一次,我接受了用 Rust 重新實現它的挑戰。我希望這篇文章能為那些對 Rust 開發有興趣的人提供有用的參考。

關於維迪

https://github.com/sachaos/viddy

Viddy 是作為類 Unix 作業系統中 watch 命令的現代替代品而開發的。除了 watch 指令的基本功能外,Viddy 還提供了以下關鍵功能,這些功能在後面提到的示範中得到了更好的說明:

  • 尋呼機功能:允許您捲動命令的輸出。
  • 時間機器模式:使您能夠查看命令的過去輸出。
  • 類似 Vim 的按鍵綁定

原本我的目標是用 Rust 實現 Viddy,但由於技術上的挑戰,我決定優先使用我更熟悉的 Go 語言來發布。這次,我克服了這些挑戰,終於實現了我最初的目標,讓這個版本對我來說特別有意義。

示範

Release of Viddy v.Migration from Go to Rust

重寫的動機

要注意的是,我對 Go 語言本身並沒有不滿意的地方。然而,由於最初的實現更多的是概念驗證 (PoC),因此經過審查,我希望改進許多領域。這些領域已經成為修復錯誤和擴展功能的障礙。從頭開始重建計畫的日益強烈的願望是一個重要的動力。

此外,我對 Rust 有著濃厚的興趣,隨著我學習這門語言的進步,我想將我的知識應用到一個真正的專案中。雖然我透過書本學習過 Rust,但我發現如果沒有實務經驗,要真正掌握語言的獨特功能並獲得掌握感是很有挑戰性的。

從重寫中獲得的見解

優先發布而不是完美實施

重新實作期間的主要焦點是決定發布的優先順序。我決定推遲記憶體使用和程式碼簡潔性等優化,並致力於盡快發布版本,而不是陷入實現最佳實現的困境。雖然這種方法可能不值得誇耀,但它讓我能夠用不熟悉的語言完成重寫,而不會灰心喪志。

例如,在這個階段,我使用頻繁克隆的方式實現了程式碼,而沒有充分考慮所有權。有很大的優化空間,所以該專案還有很大的改進潛力!

此外,有很多部分我可以用方法鏈寫得更優雅。我相信使用方法鏈可以減少 if 和 for 語句的使用,使程式碼更具聲明性。然而,我有限的 Rust 詞彙量,加上我不願意做更多的研究,導致我現在以簡單的方式實現了許多部分。

此版本發布後,我計劃重新審視所有權、執行最佳化並重構程式碼以解決這些問題。如果您碰巧查看程式碼並發現任何可以改進的地方,如果您能提出問題或提交 PR 來分享您的見解,我將不勝感激!

用 Rust 重寫的優點和缺點

在遷移到 Rust 的過程中,我注意到了與 Go 相比的一些優點和缺點。這些只是我的印象,由於我還是 Rust 的初學者,我可能會有一些誤解。如果您發現任何錯誤或誤解,我將不勝感激您的回饋!

?傳播錯誤

在 Rust 中,傳播錯誤可讓您編寫簡潔的程式碼,並在錯誤發生時儘早返回。在 Go 中,可以傳回錯誤的函數是這樣定義的:

func run() error {
    // cool code
}

當你呼叫這個函數時,你會像這樣處理錯誤。例如,如果發生錯誤,您可以提前將錯誤傳回給呼叫者:

func caller() error {
    err := run()
    if err != nil {
        return err
    }

    fmt.Println("Success")
    return nil
}

在 Rust 中,可以回傳錯誤的函數是這樣寫的:

use anyhow::Result;

fn run() -> Result {
    // cool code
}

如果你想在呼叫函數的早期回傳錯誤,你可以使用 ? 來簡潔地寫它。操作員:

fn caller() -> Result {
    run()?;
    println!("Success");
    return Ok(());
}

一開始,我對這個語法有點困惑,但是一旦習慣了它,我發現它非常簡潔和方便。

? Option Type

In Go, it's common to use pointer types to represent nullable values. However, this approach is not always safe. I often encountered runtime errors when trying to access nil elements. In Rust, the Option type allows for safe handling of nullable values. For example:

fn main() {
    // Define a variable of Option type
    let age: Option<u32> = Some(33);

    // Use match to handle the Option type
    match age {
        Some(value) => println!("The user's age is {}.", value),
        None => println!("The age is not set."),
    }

    // Use if let for concise handling
    if let Some(value) = age {
        println!("Using if let, the user's age is {}.", value);
    } else {
        println!("Using if let, the age is not set.");
    }

    // Set age to 20 if it's not defined
    let age = age.unwrap_or(20);
}
</u32>

As shown in the final example, the Option type comes with various useful methods. Using these methods allows for concise code without needing to rely heavily on if or match statements, which I find to be a significant advantage.

? The Joy of Writing Clean Code

It's satisfying to write clean and concise code using pattern matching, method chaining, and the mechanisms mentioned earlier. It reminds me of the puzzle-like joy that programming can bring.

For example, the following function in Viddy parses a string passed as a flag to determine the command execution interval and returns a Duration.

By using the humantime crate, the function can parse time intervals specified in formats like 1s or 5m. If parsing fails, it assumes the input is in seconds and tries to parse it accordingly.

// https://github.com/sachaos/viddy/blob/4dd222edf739a672d4ca4bdd33036f524856722c/src/cli.rs#L96-L105
fn parse_duration_from_str(s: &str) -> Result<duration> {
    match humantime::parse_duration(s) {
        Ok(d) => Ok(Duration::from_std(d)?),
        Err(_) => {
            // If the input is only a number, we assume it's in seconds
            let n = s.parse::<f64>()?;
            Ok(Duration::milliseconds((n * 1000.0) as i64))
        }
    }
}
</f64></duration>

I find it satisfying when I can use match to write code in a more declarative way. However, as I will mention later, this code can still be shortened and made even more declarative.

? Fewer Runtime Errors

Thanks to features like the Option type, which ensure a certain level of safety at compile time, I found that there were fewer runtime errors during development. The fact that if the code compiles, it almost always runs without issues is something I truly appreciate.

? Helpful Compiler

For example, let's change the argument of the function that parses a time interval string from &str to str:

fn parse_duration_from_str(s: str /* Before: &str */) -> Result<duration> {
    match humantime::parse_duration(s) {
        Ok(d) => Ok(Duration::from_std(d)?),
        Err(_) => {
            // If the input is only a number, we assume it's in seconds
            let n = s.parse::<f64>()?;
            Ok(Duration::milliseconds((n * 1000.0) as i64))
        }
    }
}
</f64></duration>

When you try to compile this, you get the following error:

error[E0308]: mismatched types
   --> src/cli.rs:97:37
    |
97  |     match humantime::parse_duration(s) {
    |           ------------------------- ^ expected `&str`, found `str`
    |           |
    |           arguments to this function are incorrect
    |
note: function defined here
   --> /Users/tsakao/.cargo/registry/src/index.crates.io-6f17d22bba15001f/humantime-2.1.0/src/duration.rs:230:8
    |
230 | pub fn parse_duration(s: &str) -> Result<duration error> {
    |        ^^^^^^^^^^^^^^
help: consider borrowing here
    |
97  |     match humantime::parse_duration(&s) {
    |                                     +
</duration>

As you can see from the error message, it suggests that changing the s argument in the humantime::parse_duration function to &s might fix the issue. I found the compiler’s error messages to be incredibly detailed and helpful, which is a great feature.

? The Stress of Thinking "Could This Be Written More Elegantly?"

Now, let's move on to some aspects that I found a bit challenging.

This point is closely related to the satisfaction of writing clean code, but because Rust is so expressive and offers many ways to write code, I sometimes felt stressed thinking, "Could I write this more elegantly?" In Go, I often wrote straightforward code without overthinking it, which allowed me to focus more on the business logic rather than the specific implementation details. Personally, I saw this as a positive aspect. However, with Rust, the potential to write cleaner code often led me to spend more mental energy searching for better ways to express the logic.

For example, when I asked GitHub Copilot about the parse_duration_from_str function mentioned earlier, it suggested that it could be shortened like this:

fn parse_duration_from_str(s: &str) -> Result<duration> {
    humantime::parse_duration(s)
        .map(Duration::from_std)
        .or_else(|_| s.parse::<f64>().map(|secs| Duration::milliseconds((secs * 1000.0) as i64)))
}
</f64></duration>

The match expression is gone, and the code looks much cleaner—it's cool. But because Rust allows for such clean code, as a beginner still building my Rust vocabulary, I sometimes felt stressed, thinking I could probably make my code even more elegant.

Additionally, preferences for how clean or "cool" code should be can vary from person to person. I found myself a bit unsure of how far to take this approach. However, this might just be a matter of experience and the overall proficiency of the team.

? Smaller Standard Library Compared to Go

As I’ll mention in a later section, I found that Rust’s standard library feels smaller compared to Go’s. In Go, the standard library is extensive and often covers most needs, making it a reliable choice. In contrast, with Rust, I often had to rely on third-party libraries.

While using third-party libraries introduces some risks, I’ve come to accept that this is just part of working with Rust.

I believe this difference may stem from the distinct use cases for Rust and Go. This is just a rough impression, but it seems that Go primarily covers web and middleware applications, while Rust spans a broader range, including web, middleware, low-level programming, systems programming, and embedded systems. Developing a standard library that covers all these areas would likely be quite costly. Additionally, since Rust’s compiler is truly outstanding, I suspect that a significant amount of development resources have been focused there.

? Things I Don’t Understand or Find Difficult

Honestly, I do find Rust difficult at times, and I realize I need to study more. Here are some areas in Viddy that I’m using but haven’t fully grasped yet:

  • Concurrent programming and asynchronous runtimes
  • How to do Dependency Injection
  • The "magic" of macros

Additionally, since the language is so rich in features, I feel there’s a lot I don’t even know that I don’t know. As I continue to maintain Viddy, I plan to experiment and study more to deepen my understanding.

Rust vs. Go by the Numbers

While it’s not entirely fair to compare the two languages, since the features provided aren’t exactly the same, I thought it might be interesting to compare the number of lines of source code, build times, and the number of dependencies between Rust and Go. To minimize functional differences, I measured using the RC version of Viddy (v1.0.0-rc.1), which does not include the feature that uses SQLite. For Go, I used the latest Go implementation release of Viddy (v0.4.0) for the measurements.

Lines of Source Code

As I’ll mention later, the Rust implementation uses a template from the Ratatui crate, which is designed for TUI development. This template contributed to a significant amount of generated code. Additionally, some features have been added, which likely resulted in the higher line count. Generally, I found that Rust allows for more expressive code with fewer lines compared to Go.

Lines of Code
Go 1987
Rust 4622
Go
❯ tokei
===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 Go                      8         1987         1579           43          365
 Makefile                1           23           18            0            5
-------------------------------------------------------------------------------
(omitted)
===============================================================================
 Total                  10         2148         1597          139          412
Rust
❯ tokei
===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
(omitted)
-------------------------------------------------------------------------------
 Rust                   30         4622         4069           30          523
 |- Markdown             2           81            0           48           33
 (Total)                           4703         4069           78          556
===============================================================================
 Total                  34         4827         4132          124          571
===============================================================================

Build Time Comparison

The Rust implementation includes additional features and more lines of code, so it’s not a completely fair comparison. However, even considering these factors, it’s clear that Rust builds are slower than Go builds. That said, as mentioned earlier, Rust’s compiler is extremely powerful, providing clear guidance on how to fix issues, so this slower build time is somewhat understandable.

Go Rust
Initial Build 10.362s 52.461s
No Changes Build 0.674s 0.506s
Build After Changing Code 1.302s 6.766s
Go
# After running go clean -cache
❯ time go build -ldflags="-s -w" -trimpath
go build -ldflags="-s -w" -trimpath  40.23s user 11.83s system 502% cpu 10.362 total

# Subsequent builds
❯ time go build -ldflags="-s -w" -trimpath
go build -ldflags="-s -w" -trimpath  0.54s user 0.83s system 203% cpu 0.674 total

# After modifying main.go
❯ time go build -ldflags="-s -w" -trimpath
go build -ldflags="-s -w" -trimpath  1.07s user 0.95s system 155% cpu 1.302 total
Rust
# After running cargo clean
❯ time cargo build --release
...(omitted)
    Finished `release` profile [optimized] target(s) in 52.36s
cargo build --release  627.85s user 45.07s system 1282% cpu 52.461 total

# Subsequent builds
❯ time cargo build --release
    Finished `release` profile [optimized] target(s) in 0.40s
cargo build --release  0.21s user 0.23s system 87% cpu 0.506 total

# After modifying main.rs
❯ time cargo build --release
   Compiling viddy v1.0.0-rc.0
    Finished `release` profile [optimized] target(s) in 6.67s
cargo build --release  41.01s user 1.13s system 622% cpu 6.766 total

Comparison of Non-Standard Library Dependencies

In Go, I tried to rely on the standard library as much as possible. However, as mentioned earlier, Rust's standard library (crates) is smaller compared to Go's, leading to greater reliance on external crates. When we look at the number of libraries Viddy directly depends on, the difference is quite noticeable:

Number of Dependencies
Go 13
Rust 38

例如,在Go中,標準函式庫支援JSON序列化和反序列化,但在Rust中,您需要使用serde和serde_json等第三方套件。此外,非同步運行時有多種選項,您需要自行選擇和整合它們。雖然有些函式庫可以被視為事實上的標準,但對第三方函式庫的嚴重依賴引起了對維護成本增加的擔憂。

也就是說,在 Rust 中,調整心態並更加開放地依賴外部板條箱似乎是明智之舉。

其他主題

Ratatui 模板很方便

對於這個項目,我使用了一個名為 Ratatui 的套件在 Rust 中建立 TUI 應用程式。 Ratatui 提供的範本我覺得很有用,所以我想在這裡介紹一下。

與 GUI 應用程式類似,TUI 應用程式是事件驅動的。例如,當按下某個鍵時,會觸發一個事件,並執行某些操作。 Ratatui 提供了在終端上渲染 TUI 區塊的功能,但它本身不處理事件。因此,您需要建立自己的機制來接收和處理事件。

Ratatui 提供的範本從一開始就包含這種結構,讓您可以快速建立應用程式。此外,這些範本還附帶使用 GitHub Actions 的 CI/CD 設定、鍵映射和樣式配置,可透過讀取檔案進行自訂。

如果您打算用 Rust 建立 TUI,我強烈建議您考慮使用這些範本。

呼籲在社群和 Reddit 上進行 RC 測試

為了讓社群知道 Viddy v1.0.0 是在 Rust 中重新實現的版本,我透過 GitHub Issue 和 Reddit 宣布了這一點。幸運的是,這帶來了各種回饋和錯誤報告,有些貢獻者甚至自己發現問題並提交了 PR。如果沒有這個社群的支持,我發布的版本可能仍然存在許多錯誤。

這段經歷讓我想起了開源開發的樂趣。它增強了我的動力,我衷心感謝社區的幫助。

Viddy 的新功能

有一段時間,Viddy 使用者請求一項功能,允許他們保存命令輸出的歷史記錄並在以後查看它們。作為回應,我們在此版本中實現了「回溯」功能,將執行結果保存在 SQLite 中,允許您在命令完成後重新啟動 Viddy 並查看結果。此功能可以更輕鬆地與其他人共用命令輸出的變更歷史記錄。

順便說一句,「Viddy」這個名字本身就是對電影的致敬,我計劃繼續將電影相關的主題融入專案中。我特別喜歡這個新功能的名稱“回顧”,因為它與這個主題相符。另外,日本動畫電影Look Back也非常棒。

示範

Release of Viddy v.Migration from Go to Rust

關於圖示

目前,Viddy 使用 Gopher 圖標,但由於實作語言已切換為 Rust,這可能會造成一些混亂。然而,這個圖標非常棒,所以我打算保持原樣。 ?

「Viddy well,Gopher,viddy well」這句話現在可能也有稍微不同的意思。

結論

透過將 Viddy 從 Go 重寫為 Rust 的挑戰,我能夠深入探索每種語言的差異和特點。 Rust 的錯誤傳播和 Option 類型等功能已被證明對於編寫更安全、更簡潔的程式碼非常有用。另一方面,Rust 的表達能力有時會成為壓力的來源,尤其是當我覺得有必要編寫盡可能最優雅的程式碼​​時。此外,Rust 中較小的標準庫被認為是一個新的挑戰。

儘管存在這些挑戰,但優先考慮發布並專注於讓某些功能可用,使得重寫能夠取得進展。社群對 RC 版本測試和改進的支援也是一個重要的動力。

展望未來,我計劃繼續使用 Rust 開發和維護 Viddy,以進一步提高我的語言技能。我希望這篇文章能為那些考慮使用 Rust 的人提供有用的參考。最後,如果您發現 Viddy 的程式碼有任何需要改進的地方,我將非常感謝您的回饋或 PR!

https://github.com/sachaos/viddy

以上是發布 Viddy v.Migration 從 Go 到 Rust的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Golang行動:現實世界中的示例和應用程序Golang行動:現實世界中的示例和應用程序Apr 12, 2025 am 12:11 AM

Golang在实际应用中表现出色,以简洁、高效和并发性著称。1)通过Goroutines和Channels实现并发编程,2)利用接口和多态编写灵活代码,3)使用net/http包简化网络编程,4)构建高效并发爬虫,5)通过工具和最佳实践进行调试和优化。

Golang:Go編程語言解釋了Golang:Go編程語言解釋了Apr 10, 2025 am 11:18 AM

Go語言的核心特性包括垃圾回收、靜態鏈接和並發支持。 1.Go語言的並發模型通過goroutine和channel實現高效並發編程。 2.接口和多態性通過實現接口方法,使得不同類型可以統一處理。 3.基本用法展示了函數定義和調用的高效性。 4.高級用法中,切片提供了動態調整大小的強大功能。 5.常見錯誤如競態條件可以通過gotest-race檢測並解決。 6.性能優化通過sync.Pool重用對象,減少垃圾回收壓力。

Golang的目的:建立高效且可擴展的系統Golang的目的:建立高效且可擴展的系統Apr 09, 2025 pm 05:17 PM

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

SQL排序中ORDER BY語句結果為何有時看似隨機?SQL排序中ORDER BY語句結果為何有時看似隨機?Apr 02, 2025 pm 05:24 PM

關於SQL查詢結果排序的疑惑學習SQL的過程中,常常會遇到一些令人困惑的問題。最近,筆者在閱讀《MICK-SQL基礎�...

技術棧收斂是否僅僅是技術棧選型的過程?技術棧收斂是否僅僅是技術棧選型的過程?Apr 02, 2025 pm 05:21 PM

技術棧收斂與技術選型的關係在軟件開發中,技術棧的選擇和管理是一個非常關鍵的問題。最近,有讀者提出了...

如何在Go語言中使用反射對比並處理三個結構體的差異?如何在Go語言中使用反射對比並處理三個結構體的差異?Apr 02, 2025 pm 05:15 PM

Go語言中如何對比並處理三個結構體在Go語言編程中,有時需要對比兩個結構體的差異,並將這些差異應用到第�...

在Go語言中如何查看全局安裝的包?在Go語言中如何查看全局安裝的包?Apr 02, 2025 pm 05:12 PM

在Go語言中如何查看全局安裝的包?在使用Go語言開發過程中,經常會使用go...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用