I recently started another attempt at writing an RSS reader. I've started this project a bunch of times, using Electron and Tauri and Django and now back to Tauri. One thing that kept me from embracing Tauri is that I didn't feel comfortable writing Rust. I've used it for projects in the past, but I've never used it often enough to build up the muscle memory of using it. This post is an attempt at writing down some guidelines I've set for myself to be productive without falling off the complexity cliff of Rust's more advanced features.
using String
I think this is a pretty common refrain, but it still took time for me to accept it. Steve Klabnik's recent post on using String
and &str
hit the right combination of "really clear distillation of material" and "I've seen some version of this advice so many times in so many ways and this time it finally stuck". However, while I still clone values a lot, I've substituted the recommendation of using to_string()
into a more general-purpose rule: use into()
.
from, into, and their try counterparts
Type safety is a beautiful thing. Type safety can also be a fragile thing. I often imagine my programs as a series of pipes; I'm just setting up a way for data to flow from one place to another. However, at some point those pipes need to line up to other pipes. The types I've constructed must become other types. In languages that provide little tooling for types, these connections often feel like taping two pipes together when what I really want is an appopriate fitting. In Rust, I see the From
and Into
traits (and their Try
counterparts) as those fittings. To me, type conversions are part of the accidental complexity introduced by type systems, and these traits allow me to isolate that complexity from the rest of my code. Need to coerce a struct into a similar but different struct? Is there the possibility of an error? TryInto
has my back. To bring this back to the example of converting &str
to String
, I usually just fall back to calling .into()
whenever I need to go from reference to owned.
understanding async
This isn't really a guideline, but a shoutout to a few resources that helped me get a place of reasonable comfort with async Rust. Two notable written resources are the Async Rust Book and fasterthanlime's articles. The async book has clear, concise examples of async things and I mostly use it as a reference. The fasterthanlime articles are great, engaging deep-dives into some specific Rust functionality. The async posts I really liked are a few years old now, but I think they hold up pretty well.
Another resource came out of reading a bit of Zero to Production in Rust: cargo-expand
. This tool expands proc macros and I've found it extremely illuminating. For example, it provided a lightbulb moment when I could see that #[tokio::main]
transforms async fn main
into a synchronous main function that starts and polls the async code within. I haven't used this tool too often, but I feel it'll be an important part of my Rust experience.
--
These are the things that have helped me be a little more comfortable with Rust recently. I hope I was able to expand a bit on how they've changed or augmented my mental model of Rust. These are not meant as a final destination of my Rust knowledge, but they let me use Rust enough that I start to encounter the second-level issues of using Rust, which are then addressed by the more advanced features I alluded to earlier.