Keluaran Viddy v.Migration daripada Go to Rust

WBOY
Lepaskan: 2024-08-22 19:05:33
asal
770 orang telah melayarinya

pengenalan

Dalam artikel ini, saya ingin berkongsi pengalaman dan cerapan saya yang diperoleh semasa pelaksanaan semula Viddy, alat TUI yang telah saya bangunkan, daripada Go to Rust untuk keluaran v1.0.0. Viddy pada asalnya dibangunkan sebagai versi moden arahan jam tangan, tetapi kali ini, saya menyahut cabaran untuk melaksanakannya semula dalam Rust. Saya harap artikel ini menjadi rujukan berguna bagi mereka yang berminat untuk membangunkan dengan Rust.

Soal Viddy

https://github.com/sachaos/viddy

Viddy telah dibangunkan sebagai alternatif moden kepada arahan jam tangan yang terdapat dalam sistem pengendalian seperti Unix. Sebagai tambahan kepada fungsi asas arahan jam tangan, Viddy menawarkan ciri utama berikut, yang digambarkan dengan lebih baik dalam demo yang dinyatakan kemudian:

  • Fungsi kelui: Membolehkan anda menatal keluaran perintah.
  • Mod mesin masa: Membolehkan anda menyemak output arahan yang lalu.
  • Ikatan kekunci seperti vim

Pada asalnya, saya berhasrat untuk melaksanakan Viddy dalam Rust, tetapi disebabkan cabaran teknikal, saya memutuskan untuk mengutamakan keluaran dengan menggunakan Go, bahasa yang lebih saya kenali. Kali ini, saya dapat mengatasi cabaran tersebut dan akhirnya merealisasikan matlamat awal saya, menjadikan keluaran ini amat bermakna kepada saya.

Demo

Release of Viddy v.Migration from Go to Rust

Motivasi untuk Penulisan Semula

Adalah penting untuk ambil perhatian bahawa saya tidak mempunyai rasa tidak puas hati dengan bahasa Go itu sendiri. Walau bagaimanapun, memandangkan pelaksanaan asal adalah lebih kepada Bukti Konsep (PoC), terdapat banyak bidang yang, setelah disemak, saya ingin perbaiki. Kawasan ini telah menjadi halangan untuk membetulkan pepijat dan melanjutkan fungsi. Keinginan yang semakin meningkat untuk membina semula projek dari awal ini merupakan pendorong yang penting.

Selain itu, saya mempunyai minat yang kuat terhadap Rust dan, semasa saya maju dalam pembelajaran bahasa, saya ingin menggunakan pengetahuan saya untuk projek sebenar. Walaupun saya telah mempelajari Rust melalui buku, saya mendapati sukar untuk benar-benar memahami ciri unik bahasa itu dan memperoleh rasa penguasaan tanpa pengalaman langsung.

Cerapan yang Diperolehi daripada Penulisan Semula

Utamakan Pelepasan Daripada Perlaksanaan Sempurna

Tumpuan utama semasa pelaksanaan semula adalah untuk mengutamakan keluaran. Daripada terperangkap dalam mencapai pelaksanaan yang paling optimum, saya memutuskan untuk menangguhkan pengoptimuman seperti penggunaan memori dan ketepatan kod dan bertujuan untuk mengeluarkan keluaran secepat mungkin. Walaupun pendekatan ini mungkin bukan sesuatu yang boleh dibanggakan, ia membolehkan saya meneruskan penulisan semula dalam bahasa yang tidak dikenali tanpa berkecil hati.

Sebagai contoh, pada peringkat ini, saya melaksanakan kod menggunakan pengklonan yang kerap tanpa mempertimbangkan pemilikan sepenuhnya. Terdapat banyak ruang untuk pengoptimuman, jadi projek ini mempunyai banyak potensi untuk diperbaiki!

Selain itu, terdapat banyak bahagian yang boleh saya tulis dengan lebih elegan menggunakan rantai kaedah. Saya percaya bahawa menggunakan rantai kaedah boleh mengurangkan penggunaan jika dan untuk pernyataan, menjadikan kod lebih deklaratif. Walau bagaimanapun, perbendaharaan kata Rust saya yang terhad, digabungkan dengan keengganan saya untuk melakukan lebih banyak penyelidikan, mendorong saya untuk melaksanakan banyak bahagian dengan cara yang mudah buat masa ini.

Setelah keluaran ini dikeluarkan, saya merancang untuk menyemak semula pemilikan, melakukan pengoptimuman dan memfaktorkan semula kod untuk menangani kebimbangan ini. Jika anda kebetulan menyemak kod dan melihat mana-mana kawasan yang boleh dipertingkatkan, saya amat menghargai jika anda boleh membuka isu atau menyerahkan PR untuk berkongsi pandangan anda!

Kebaikan dan Keburukan Menulis Semula dalam Karat

Dalam proses berhijrah ke Rust, saya telah melihat beberapa kebaikan dan keburukan berbanding dengan Go. Ini hanyalah tanggapan saya, dan kerana saya masih pemula dengan Rust, saya mungkin mempunyai beberapa salah faham. Jika anda melihat sebarang kesilapan atau salah tanggapan, saya akan menghargai maklum balas anda!

? Menyebarkan Ralat

Dalam Rust, ralat penyebaran membolehkan anda menulis kod ringkas yang kembali awal apabila ralat berlaku. Dalam Go, fungsi yang boleh mengembalikan ralat ditakrifkan seperti ini:

func run() error {
    // cool code
}
Salin selepas log masuk

Dan apabila anda memanggil fungsi ini, anda mengendalikan ralat seperti ini. Contohnya, jika ralat berlaku, anda mungkin mengembalikan ralat awal kepada pemanggil:

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

    fmt.Println("Success")
    return nil
}
Salin selepas log masuk

Dalam Rust, fungsi yang boleh mengembalikan ralat ditulis seperti ini:

use anyhow::Result;

fn run() -> Result<()> {
    // cool code
}
Salin selepas log masuk

Dan jika anda ingin mengembalikan ralat awal dalam fungsi panggilan, anda boleh menulisnya dengan ringkas menggunakan ? pengendali:

fn caller() -> Result<()> {
    run()?;
    println!("Success");
    return Ok(());
}
Salin selepas log masuk

Pada mulanya, saya agak keliru dengan sintaks ini, tetapi setelah saya membiasakannya, saya dapati ia sangat ringkas dan mudah.

? 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);
}
Salin selepas log masuk

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))
        }
    }
}
Salin selepas log masuk

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))
        }
    }
}
Salin selepas log masuk

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) {
    |                                     +
Salin selepas log masuk

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)))
}
Salin selepas log masuk

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
Salin selepas log masuk
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
===============================================================================
Salin selepas log masuk

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
Salin selepas log masuk
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
Salin selepas log masuk

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

Sebagai contoh, dalam Go, pensirilan JSON dan penyahsirisian disokong oleh pustaka standard, tetapi dalam Rust, anda perlu menggunakan peti pihak ketiga seperti serde dan serde_json. Selain itu, terdapat pelbagai pilihan untuk masa jalan tak segerak, dan anda perlu memilih dan menyepadukannya sendiri. Walaupun terdapat perpustakaan yang boleh dianggap sebagai piawaian de facto, pergantungan berat pada perpustakaan pihak ketiga menimbulkan kebimbangan tentang peningkatan kos penyelenggaraan.

Maksudnya, dalam Rust, nampaknya bijak untuk melaraskan minda anda dan lebih terbuka untuk bergantung pada peti luaran.

Topik Lain

Templat Ratatui Mudah

Untuk projek ini, saya menggunakan peti yang dipanggil Ratatui untuk membina aplikasi TUI dalam Rust. Ratatui menawarkan templat yang saya dapati sangat berguna, jadi saya ingin memperkenalkannya di sini.

Sama seperti aplikasi GUI, aplikasi TUI adalah dipacu peristiwa. Contohnya, apabila kekunci ditekan, peristiwa dicetuskan dan beberapa tindakan dilakukan. Ratatui menyediakan fungsi untuk membuat sekatan TUI pada terminal, tetapi ia tidak mengendalikan acara dengan sendirinya. Oleh itu, anda perlu mencipta mekanisme anda sendiri untuk menerima dan mengendalikan acara.

Templat yang disediakan oleh Ratatui termasuk struktur jenis ini dari awal, membolehkan anda membina aplikasi dengan cepat. Selain itu, templat disertakan dengan persediaan CI/CD menggunakan Tindakan GitHub, pemetaan kunci dan konfigurasi gaya yang boleh disesuaikan dengan membaca daripada fail.

Jika anda bercadang untuk mencipta TUI dalam Rust, saya amat mengesyorkan agar anda mempertimbangkan penggunaan templat ini.

Memanggil Ujian RC dalam Komuniti dan di Reddit

Untuk memberitahu komuniti bahawa Viddy v1.0.0 ialah versi yang dilaksanakan semula dalam Rust, saya mengumumkannya melalui Isu GitHub dan pada Reddit. Nasib baik, ini menghasilkan pelbagai maklum balas dan laporan pepijat, malah sesetengah penyumbang menemui isu sendiri dan menyerahkan PR. Tanpa sokongan komuniti ini, saya mungkin telah mengeluarkan versi dengan banyak pepijat masih wujud.

Pengalaman ini mengingatkan saya tentang kegembiraan pembangunan sumber terbuka. Ia meningkatkan motivasi saya, dan saya amat berterima kasih atas bantuan komuniti.

Ciri Baharu dalam Viddy

Untuk beberapa lama, pengguna Viddy telah meminta ciri yang membolehkan mereka menyimpan sejarah output arahan dan menyemaknya kemudian. Sebagai tindak balas, kami telah melaksanakan ciri "lihat kembali" dalam keluaran ini yang menyimpan hasil pelaksanaan dalam SQLite, membolehkan anda melancarkan semula Viddy selepas arahan selesai dan menyemak keputusan. Ciri ini memudahkan untuk berkongsi sejarah perubahan output arahan dengan orang lain.

Omong-omong, nama "Viddy" itu sendiri adalah tanda kepada pawagam, dan saya bercadang untuk terus memasukkan tema berkaitan filem ke dalam projek itu. Saya amat menggemari nama "lihat semula" untuk ciri baharu ini, kerana ia sejajar dengan tema ini. Selain itu, filem animasi Jepun Lihat Belakang sememangnya hebat.

Demo

Release of Viddy v.Migration from Go to Rust

Mengenai Ikon

Pada masa ini, Viddy menggunakan ikon Gopher, tetapi memandangkan bahasa pelaksanaan telah bertukar kepada Rust, ini mungkin menyebabkan kekeliruan. Walau bagaimanapun, ikon itu hebat, jadi saya bercadang untuk mengekalkannya sebagaimana adanya. ?

Frasa "Viddy well, Gopher, viddy well" mungkin mempunyai makna yang sedikit berbeza sekarang juga.

Kesimpulan

Melalui cabaran untuk menulis semula Viddy daripada Go to Rust, saya dapat meneroka dengan mendalam perbezaan dan ciri setiap bahasa. Ciri seperti penyebaran ralat Rust dan jenis Pilihan terbukti sangat berguna untuk menulis kod yang lebih selamat dan ringkas. Sebaliknya, kuasa ekspresif Rust kadangkala menjadi sumber tekanan, terutamanya apabila saya rasa terpaksa menulis kod yang paling elegan yang mungkin. Selain itu, perpustakaan standard yang lebih kecil di Rust telah diiktiraf sebagai cabaran baharu.

Walaupun menghadapi cabaran ini, mengutamakan keluaran dan memfokuskan pada mendapatkan sesuatu yang berfungsi di luar sana membolehkan penulisan semula berkembang. Sokongan daripada komuniti dalam menguji dan menambah baik versi RC juga merupakan pendorong penting.

Melangkah ke hadapan, saya merancang untuk terus membangun dan mengekalkan Viddy in Rust untuk meningkatkan lagi kemahiran saya dengan bahasa. Saya harap artikel ini berfungsi sebagai rujukan berguna bagi mereka yang mempertimbangkan untuk mengambil Rust. Akhir sekali, jika anda melihat mana-mana bahagian untuk penambahbaikan dalam kod Viddy, saya amat menghargai maklum balas atau PR anda!

https://github.com/sachaos/viddy

Atas ialah kandungan terperinci Keluaran Viddy v.Migration daripada Go to Rust. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan