https://github.com/postmates/cernan
Cernan has to be sparing in its memory consumption, be a good citizen with regard to its CPU impact–at Postmates we deploy it as a ride-along with application servers--and be correct even though there's changes being made at a punishing pace.
When I started cernan I'd been following Rust for a while but had _not_ written anything of note in it. My estimation, though, was that Rust would be a better fit for cernan than C++:
* It'd be easier for colleagues knowing neither Rust nor C++ to review Rust code.
* If I got hit by a bus, the Rust compiler would make stronger behaviour constraints for me.
You can see in the first month https://github.com/postmates/cernan/commits/master?after=Y3V... that I flailed around a bit. I was internalizing the borrow checker's view of Rust's memory model, getting used to the tooling and the ecosystem of crates available. One thing I particularly had issues with from the get go and have _kind of_ resolved are many wee allocations inside cernan. In some sense they're inevitable but my early ignorance exacerbated the problem: I'd clone to convince the borrow checker to let me move on, would construct new containers rather than pass new ones in to be refilled and the like. Also, strings. Lots and lots of strings everywhere.
After the first month, toward the back half of the second I felt like I was flying finally. Still plenty I needed to internalize or learn about but I had a cohesive mental model in which to incorporate new information.
I've been teaching myself Rust in the evenings for a couple of weeks now and have found the process maddeningly confusing and slow. Even with the Book, which is fine, I ended up getting into rage induced clone/to_owned/etc keyboard mashing dances with the borrow checker. If it weren't so easy to do this kind of dev with emacs, I might have given up earlier.
On the other hand I occasionally get these moments where my code just "clicks" and it feels good and _seems_ elegant. But, pedagogically speaking, at this point I'm just trying to learn through immersion, not through theory and understanding.
Honestly I'd probably be way done with this little project if I had started in Go, but I think I'm going to stick with Rust for a while.
Oh hey, that's a pretty good way of putting it. For me, internalizing the borrow checker is where I had the hardest time. Much of what the borrow checker formalizes is best-practices in C++ but it's a surprisingly different experience to have a robot come and check what you ought to have been doing anyhow. Maybe I found it even harder to adapt because while my C++ is passable I'm no guru.
But! Immersion for sure works and I'm _really_ glad I stuck it out. Now that I'm on the other side I can see that the borrow checker is a novel invention and novel inventions are _very_ difficult to explain at first.
This is where I'm at right now. When the borrow checker yells at me, I find I can generally fix it with a .clone() or a .to_string(). I'm new enough with the borrow checker that I'm not quite sure when such cloning is necessary and when I'm unnecessarily allocating more memory -- or more importantly, what I should be doing instead when it's not necessary -- but I hope that as I write more code, I'll get better at it :)
A bad habit that I've fallen into is to needlessly allocate in order to avoid having to think about lifetimes. As in, better change that struct to make that str a String -- and then not have to worry about lifetimes at all. That's just laziness, though...
Make it work, then make it correct, then make it fast. Chances are you'll never need that last step for most code you write.
Edit: another thing you'll learn with practice is when to make the right trade offs. Rust has lots of types in the standard library that allow you to pick and choose which guarantees you want the compiler to uphold and whether to do it at compile time or run time. Check out "Choosing your Guarantees" in the new version of the rust book [1]
[1] https://doc.rust-lang.org/beta/book/choosing-your-guarantees...
You have to "notify the memory allocator" in every language but in Rust you don't pay for the extra GC overhead unless you explicitly opt into RC, Arc, or your own GC. The point is that in a low level language, allocating and copying to maintain safety guarantees is fine because you have finr grained control over what you pay for. For a beginner, it's more important to get a feel for the borrow checker and cloning allows someone to get a positive feedback loop until they figure out how to structure their code to work with the borrow checker instead of against it.
Cloning is definitely a perfectly good way to get started, but it shouldn't be dressed up as something it's not nor should Rust itself.
I'm still missing an IDE that spits out inferred types including generics when hovering in the middle of chained method calls. Especially in iterators it's often hard to know what it's currently passsing as arguments into some closure.
That situation is much much better in java-land.
(A more general description of this work starts around 40:09)
It took me a month (of my free time) to start feeling like I can write code in Rust. It felt like I was learning programming all over again.
There were several reasons for that: first, I assumed that Rust can be used the same way as Javascript, with callbacks everywhere, and second, I was used to OO patterns that can not work in Rust. So, to overcome that, I had to realize that while Rust may have modern features, it is fundamentally a low-level language; and I had to really understand what the OO patterns actually give me to find the alternatives in Rust. I think that these things have made me a better programmer overall.
I remember these distinct stages.
Rust is low-level stage: It took a while to realize that Rust structs live on stack, and the pointer to such struct can not be freely passed around like an object, because the stack frame will be gone at the end of a function.
I found out how to do virtual dispatch: the fact that borrowed traits have virtual dispatch table was missing from docs at the time (they are called Trait Objects). This is what I was actually missing from OO: a way to use the interfaces without putting everything into enums, and have a way to invert dependencies.
There is a borrow checker, but you need to think in ownership: Again, I was trying to use references as if they were objects in OO language, while the primary model is ownership. I wrote a bog post at that time discussing this: http://nercury.github.io/rust/guide/2015/01/19/ownership.htm...
Rust is more functional than it seems: At that point I found that you can model anything in Rust if you can express it as a data-flow from the input to output. Turns out, anything can be expressed as the data flow. The data that flows can be uniquely mutable, processed in parallel. I had to ditch thinking in highly shared hierarchies of objects and focus on flows that transform one set of structures to other sets of structures.
I'd say another two weeks before I was using lifetimes properly, traits, enums, etc in 'idiomatic' ways.
All in all, 4 weeks before I felt productive.
I come from C++, which helped and hurt. On the one hand, move semantics weren't new - but C++ move semantics felt like nothing* compared to Rust's. It actually really helped me internalize why you would want them in C++.
I knew that returning a pointer to the stack was bad in C++. In rust, I actually started doing that (just parent stacks), but safely.
* Such as
self.foo(&self.bar)This is coming from a background of mostly C/Java.
Once it clicks it is very simple, but developing the intuition and breaking old habits is the frustrating part.
http://cglab.ca/~abeinges/blah/too-many-lists/book/
for helping to grok the memory model. I tried to implement each chapter's data structure before I read the chapter so that I would really understand what problem the introduced memory constructs were solving.
It's definitely not an easy language to learn, but once you get past the memory model stuff I think the learning curve at least gets a little flatter.
However if you want to create any graph like data structures you need to go into unsafe rust.
For example, the std doubly linked list uses unsafe with error handling to do inserts because the compiler cannot be sure that an insert is safe.
In my opinion Rust's safety is overhyped and entirely dependent on good implementation of unsafe code. And judging from the liked list example there is no way for all rust code to be intrinsically safe.
That's short-sighted. Of course it's "entirely dependent on good implementation of unsafe code".
But that's inconsequential, as unsafe code in a library, especially one used by everybody, is much easier to make safe than unsafe code all around.
And after you trust an unsafe lib you use to be sound, your own safe-only code can never breach that safety. You'll be guaranteed that any issue will be on the unsafe part.
It's all about compartmentalization.
...as is true of any safe language, which is dependent on a good implementation of its compiler or interpreter. Rust just happens to move some of that into the language where it can be extended.
For about a year, I've been regularly using Rust to write real software that I actually use, and I've never had to write unsafe code. I'm trusting other people to do it, which is never not the case, no matter what programming language I'm using.
My main point is that safety rests upon the library correctness. And I can see the value of the interlocks this provides. However it is not infallible as the word safe suggests.
I'll admit that I have not grokked rust, but I did give it a reasonable try. Perhaps all my C hacking has ruined me. :)
I don't disagree with that. Sometimes I don't totally love how the Rust people market the language, and I can see how the statements they make about safety could rub their target audience the wrong way, but I don't know how I'd do it better. I really do think the language does a great job helping programmers write fast, safe, bug-free code... once they get over the learning curve.
If you ever feel like taking another crack at it, this is the reference that made the memory model stuff (which is the trickiest bit IMO) click for me:
My main issue is that I found creating graphs is unwieldy. Where in C or Python I can just whip one up.
Is there a reasonably easy way to do it without depending on a library (like petgraph)?