thespacebetweenstars.com

Rust's Journey: A Three-Year Retrospective on WebAssembly Development

Written on

Was it worth the plunge into Rust? My journey into WebAssembly began when I decided to dedicate myself fully to it. At that time, Rust was the standout choice due to its superior support for WebAssembly compilation and the robust runtimes built on it. Intrigued by the excitement surrounding Rust, I dove in headfirst.

Over the past three years, my colleagues and I have developed Wick, an application framework and runtime that positions WebAssembly as its foundational module system.

With three years of production deployments, an ebook, and around 100 packages on crates.io under my belt, I feel it’s time to share my reflections on Rust.

The Positive Aspects

You Can Do More with Less

I am an ardent supporter of test-driven development. Having previously worked with languages like Java and JavaScript, I initially approached testing in Rust similarly. However, I quickly found myself crafting tests that were almost guaranteed to succeed. Once your Rust code compiles, the language's error handling means many common test scenarios become unnecessary. By steering clear of unsafe {} blocks and panic-inducing methods like .unwrap(), you build a strong foundation that naturally avoids many pitfalls.

Rust's stringent borrow checker, sophisticated type system, and functional programming features allow for easier maintenance with reduced testing requirements. For instance, I've managed over 70,000 lines of code in the Wick project with far fewer tests than I'd typically need in other programming languages.

When writing tests becomes necessary, Rust's testing framework allows you to add them seamlessly alongside your code, making the process almost effortless.

I Write Better Code in Other Languages Now

Programming in Rust can feel akin to being in a tumultuous relationship. The compiler often admonishes you for errors that might go unnoticed in other languages. Over time, you adapt, learning to navigate the tightrope to avoid inciting the compiler’s wrath. Much like in real life, these behavioral changes linger long after the experience.

While this kind of pressure isn't a conventional means of fostering growth, it undeniably catalyzes change.

Now, I find myself uneasy writing code in other languages when things are out of order or when I neglect to check return values. I also experience undue frustration when faced with runtime errors.

Clippy Is an Excellent Tool!

Clippy, Rust's linter, deserves more than just the title of a linter. In a language where the compiler can be quite unforgiving, Clippy acts as a supportive companion rather than a mere tool.

The Rust standard library is vast and can make it challenging to locate specific functions among the multitude of granular types, traits, macros, and functions available. Many of Clippy's rules aim to identify common patterns that could be improved using standard library methods or types.

Clippy encompasses hundreds of rules that address performance, readability, and unnecessary complexity, often providing suggested code replacements. Additionally, it seems that soon you will be able to globally configure lints for your projects, a long-awaited feature that the Rust community has worked hard to implement.

The Challenges

Gaps You Must Accept

I often questioned my sanity regarding Clippy’s limitations. Surely, there must be a way to configure lints globally. Despite extensive checks, I found no solution, and this issue lingered unresolved for years.

While Clippy is impressive, this situation exemplifies a broader issue within the Rust ecosystem. I frequently encounter libraries or tools that don’t cater to my specific needs. This isn’t unusual in emerging languages, but Rust has been around long enough that it feels unusual.

In open source, early adopters typically address edge cases, refining projects for future users. Rust has consistently been ranked as the "most loved language," attracting new users. However, this influx hasn't necessarily led to significant improvements in libraries or tools; instead, it has resulted in isolated forks for specific use cases.

I can't pinpoint the cause of this stagnation. Perhaps the pressure to maintain stable APIs combined with Rust's granular type system inhibits library owners from making changes. Alternatively, writing versatile Rust code that serves a broad audience can be daunting, discouraging developers from attempting it.

Cargo, crates.io, and Project Structuring

I structured the Wick repository based on popular projects I admired. It worked well until it didn't.

While you can easily build, test, and use module-sized crates with Cargo, publishing them to crates.io introduces challenges. You cannot publish a crate unless every referenced crate is also published individually, which seems reasonable but poses a challenge for developers who naturally break large projects into smaller modules.

This limitation feels arbitrary. You can design projects in this manner, but publishing them becomes a hurdle.

Edit: Ed Page reached out to clarify that you can publish with local dev dependencies, provided that you do not specify a version in Cargo.toml.

Cargo does provide excellent workspace support, offering a better experience for managing large projects than many languages. However, it doesn't alleviate the deployment issue. Setting up workspaces can be done in various ways, but none simplify the deployment process.

The frustrations manifest in the abundance of utility crates created to facilitate workspace publishing, each compatible with a limited set of configurations. The elusive "one true way" to set up workspaces continues to elude me, leading to hours of repetitive manual tasks when publishing Wick.

Async Programming

Rust introduced asynchronous programming after its initial release, and it often feels like an afterthought. This addition can create confusion and complicate matters with difficult-to-understand error messages. When searching for solutions, you must navigate the different runtimes and their async implementations. If you wish to use an async library, you might find it incompatible with your chosen async runtime.

With two decades of JavaScript experience and a solid background in Go, async programming remains my most significant source of frustration in Rust. It’s a manageable challenge, but you must always be prepared to confront async issues head-on.

The Difficulties

Refactoring Can Be Tedious

Rust's complex type system can be both a blessing and a curse. While thinking in Rust types can be enjoyable, managing them can be a nightmare. Your data structures and function signatures may involve generics, lifetimes, and trait constraints, often resulting in more type constraints than actual code.

You must define all generics for every implementation, which can be tedious when initially coding. However, during refactoring, a minor change can lead to a cascade of complications.

Making swift progress is challenging when you need to update numerous definitions before moving forward.

Edit to clarify: The issue isn't with expressibility; rather, there's no solution in the language or tooling to minimize duplication. While there are often reasons to have identical constraints or refer to the same generics, there’s no way to create an alias or central reference. The absence of a solution doesn’t lessen the burden of duplication.

The Conclusion

I have a profound appreciation for Rust. Its versatility allows me to write system-level code alongside CLI applications, web servers, and web clients. The ability to run the same binary for an LLM both in the browser and on the command line is still astonishing to me.

I value the robustness that Rust programs can achieve. Once you grasp what Rust safeguards you against, returning to other languages can feel daunting. I briefly returned to Go and was initially enchanted by the rapid development speed, but that excitement quickly faded when I encountered runtime panics.

However, Rust isn’t without its flaws. It presents hiring challenges, has a steep learning curve, and can be rigid in terms of iteration. Troubleshooting memory and performance issues, especially with async code, can be challenging. Not all libraries adhere to safe coding practices, and the development tools often leave much to be desired. You start at a disadvantage and face numerous obstacles. If you can overcome these challenges, you’ll excel; that’s a significant "if."

Was diving into Rust worthwhile for us? The verdict is still out. We've accomplished remarkable feats as a small team but also faced substantial hurdles. Technical factors have influenced Rust's viability for our needs.

Will it be the right choice for you? If rapid iteration is a priority, probably not. However, if you have a defined scope or can absorb some initial costs, it's certainly worth considering. You'll end up with resilient software. With WebAssembly continuing to gain traction, the reality of writing flawless software once and reusing it everywhere is on the horizon.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Nvidia's Stock Split: Strategic Insights for Investors

Nvidia's 10-for-1 stock split prompts questions about investment timing and future growth in the AI market.

# An Inspiring Conversation with Erica Marie: Insights and Reflections

An engaging interview with Erica Marie, discussing her writing journey, inspirations, and valuable advice for new writers.

How I Secured a PhD Scholarship to Study in Germany

Discover how I won a PhD scholarship in Germany despite a low GPA and no publications, and learn how you can achieve the same!

Starting a Home Business on a Budget: Essential Strategies

Discover effective strategies for launching a home business on a budget, including cost-saving tips and marketing ideas.

# Navigating the Challenges of Biden's Climate Agenda

An exploration of the hurdles and dynamics impacting Biden's climate initiatives, including labor relations and political challenges.

# Navigating the Ups and Downs of Life: A Personal Reflection

A personal exploration of highs and lows, emphasizing mental health struggles and the impact of technology.

The Enduring Legacy of Francis Bacon in Science and Philosophy

An overview of Francis Bacon's profound influence on modern science and philosophy, focusing on empiricism and the scientific method.

The Journey to Finding Your Soulmate: A Personal Reflection

A personal exploration of soulmates, relationships, and self-worth through the lens of experience and growth.