

This was my experience too, until I learned a few things.
- If you’re coming from another programming language, the equivalent capabilities you’re probably used to are
Box
,dyn
, andRc
. - Dynamic dispatch (
dyn
) isn’t really necessary a lot of the time. Identify where you absolutely need it and solve everything else through other means. - You wind up with lifetime specifier problems if you try to do a lot with references (
&
). Instead, try to re-think your structs and functions using composition and clone/copy instead. It’s less efficient, but it’s easier to optimize a running program, too. - Rust
enum
,match
,if let
, and?
are weird, but are where you get the most leverage in the language. Try to master them. derive[...]
is a first-class feature with a lot of standard lib support. Always use this to make your custom types mesh with the standard lib more seamlessly.- If you are experienced with the “Design Patterns” book, you absolutely need this: https://rust-unofficial.github.io/patterns/intro.html
- Macros are an advanced feature, but help get you around limitations in generics and the type system in general. it really is worth knowing, and like the preprocessor in C/C++, isn’t avoidable at the intermediate level.
- The compiler digs deep into your code to figure out types where they’re not explicitly declared. I’ve seen it reach into the return type, call-spec, and function calls within that function, to figure out types for things. This is very hard to observe without an IDE that’s checking syntax on the fly. Lean into both of those for more readable and maintainable code.
if
andmatch
are expressions, not statements! You can use either block to evaluate to a single value, useful in composite expressions likelet
. Example;let x=if y>20 { y } else { 0 };
Or use them to return values from functions (w/o need of a return statement).
This is basically where my learning took me. I had to develop this notion that there was a preferred directionality to ownership and data flow, like “grain” in a piece of wood. Everything is easier if you go with the grain. “Tree-shaped” works too, since it basically is the call graph of a (single threaded) program.
The point where I realized all this was when I tried to do a very Python/JS-brained thing: return a closure from a function. The moment you try to “curry” values into the closure, you have to “move” them to solve for ownership, lest you bring timelines into the picture. Which isn’t always what you want in a generic and reusable function. And sure enough, the standard lib and other popular libraries want you to pass a closure to functions instead.