Hacker News new | comments | show | ask | jobs | submit login
Rust by Example (rustbyexample.com)
191 points by giis on Jan 15, 2017 | hide | past | web | favorite | 62 comments



Can anyone share some insights on how time much did it took to get comfortable with Rust? I'm getting started and crawling with concepts like ownership, mutability


I just released a big-ish open-source project in Rust that I've been working on since June 2016.

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.


> 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.

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.


> I'm just trying to learn through immersion, not through theory and understanding.

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.


> 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.

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...


This is the going advice many Rustaceans give to beginners. Cloning something in Rust is nothing like cloning in dynamic languages where you need to notify a garbage collector and allocate heap memory. Rust is fast and copying data is fast, especially if you're cloning a stack only value to satisfy the borrow checker.

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...


Cloning anything nontrivial (String, Vec, structs containing them, which is when cloning is usually used to ease things) does need to allocate. The comparison to managed languages doesn't make sense: "notifying the GC" sounds exactly like "notifying the memory allocator" in Rust/C/C++.


I should have said "as long as" instead of "especially when." When you're cloning a data structure, obviously you need to think about what you're cloning, no matter what language you do it in, and you need to know the difference between Copy and Clone. My point is that a cloning operation is still fast and modern processors are well optimized for it as long as the library author didn't put some computationally expensive or blocking operation.

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.


One of the benefits of some common GC algorithms is how efficient it is to allocate (for most allocations), meaning the GC overhead is small and possibly negative. I don't think comparing the (throughput) performance of doing allocations is where Rust wins in a convincing way: any major benefits come because the language makes it easier to avoid them totally, as you imply. In any case, the most common things people clone when learning are strings and vectors, where cloning is an O(length) operation (allocation plus copying the data), which doesn't avoid any allocation overhead and I would think counts as computationally expensive. (Notably, in managed languages with immutable strings, Rust's copies are significantly more expensive, since the former can copy in O(1).)

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.


Clone runs arbitrary Rust code; there's no way to say how expensive or not it is. That's one reason it's never called implicitly.


> getting used to the tooling

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.


This is coming! A demo from a few months ago: https://youtu.be/pTQxHIzGqFI?list=PLE7tQUdRKcybLShxegjn0xyTT...

(A more general description of this work starts around 40:09)



No, it does not seem to use information from the compiler to get the actual types. It seem like it sometimes does something based on imports or something like that. But it fails even on trivial code:

https://i.imgur.com/mPUbrFP.png


When I started out (before 1.0 release), the documentation was very poor, I was coming into Rust with 5+ years of dynamic language experience (PHP, JS), my C#/C++ knowledge was rusty.

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.


About two weeks to get the majority of language features - iterators, some aspects of ownership, structs, stop fighting the borrow checker for the common cases. I was using threads to parallelize my work within the first few days, and that was particularly cool for me.

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)


I would second this: also about 2 weeks. Interestingly I came from a JavaScript background so I'm guessing it was different aspects of the language that felt unfamiliar.


Once I had learned the syntax, it took me a few days while working on a real project with well-defined goals to internalize the borrow-checker and such. Clippy has also been a surprisingly useful tool for learning - it pointed out some things that weren't idiomatic and that also solidified some concepts for me.


https://medium.com/learning-rust/rust-the-tough-part-2ea11ed... ; This is all about Ownership, Borrowing,Lifetimes & Lifetime Elision. Might be helpful for you.


It took me 2-3 weeks of being fairly frustrated with Rust until I grokked the borrow checker. This was mostly on/off coding in my spare time at night.

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.


I think it took me a couple of weeks to be able to write practical programs (i.e. actual useful things) and a month or two more to feel really comfortable with it. I was not pursuing it on anything close to a full-time basis. I recommend this tutorial:

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.


If you want to do the basic things that rust wants you to do it doesn't take that long.

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.


>In my opinion Rust's safety is overhyped and entirely dependent on good implementation of unsafe code.

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.


> In my opinion Rust's safety is overhyped and entirely dependent on good implementation of unsafe code.

...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.


Show me a programming language that lets you manipulate raw pointers while providing the same safety guarantees as safe Rust code (i.e. have your cake and eat it too) and I'll accept this as a valid criticism of Rust. Until then, I"m going to keep thinking that Rust provides a reasonable, practical compromise.

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.


By definition manipulation of raw pointers is unsafe. So no language can do that. Sure Rust's typed pointers can be safe as long as libraries fulfill their garuntees.

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. :)


> 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 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:

http://cglab.ca/~abeinges/blah/too-many-lists/book/


I don't see why a dissenting opinion gets beaten up here... wouldn't it be better for the community to address the perceived deficiencies with solutions?

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)?


I have no experience with low level languages and i'm 4 weekends in. I feel like i'm half way there to be comfortable and some more to actually be productive.


https://medium.com/learning-rust might be helpful for you.


I learned rust while I was taking classes so this may be longer than usual, but I started compiling on the first try in about four months. It was my second language though. So if you have learned a language a few times and you aren't busy you could probably do it in a month. That's how long my professor said it took him. And I must say, once you learn rust, it's such a joy.


As a heavy ruby, go and Python user what kind of scripts or programs should I consider migrating to rust to help with learning it? Or is it really just low level stuff that I can't justify the additional complexity


When you say "low level stuff", what do you picture?


I picture things like drivers, kernel extensions, etc. I do a lot of network programming with raw sockets and custom packets, parsing Netflow / IPFIX, etc.


Nah, it doesn't have to be that low level.

Servo, for example, doesn't deal with this; it's a web browser.

Think of the use cases where you'd use C or C++, and Rust should work there.

You probably can rewrite Ruby scripts in Rust if you want, but the productivity benefit depends on the kind of script. But for the purposes of learning the language it doesn't really matter.


Anyone here using Rust on mobile? That's the potential usecase that interests me the most, but given that the tooling still has a lot to be desired, and a lot of mobile code is vendor-specific anyways, I'm not sure it makes that much more sense than just duplicating those shared parts between Swift and Java.

I could see it for small modular parts that are better suited for native code and when one doesn't want to use C, perhaps, or, of course, for infrastructure-level projects like Servo.

I just wonder if I'm barking up the wrong tree because much of the Rust interest I see seems more heavily weighted in infrastructure and backend, not in user-facing code.


great website. I think i'm going to start with C++ just so i get a foundation of what a systems language is and appreciate the innovation that Rust brings to the table.

I'd be really curious to know if website format exists for other languages.


There is a website with a similar format for the Go language: https://gobyexample.com


I'm doing the same, but with microcontrollers and C instead. To me C++ just seemed to big of a mountain to climb when its just an intermediary step. All the best! There is so much to learn.


as a noob interested in rust, what is its primary application? why do people use rust vs another language?


I work at a startup with large amounts of JavaScript and Ruby, but no existing investment in a compiled language prior to Rust.

Adding C++ to our toolchain would result in a nasty culture shock. Ruby and JavaScript have modern dependency managers (bundler and npm/yarn). They're easy to get working cross-platform. They almost never segfault.

C++ would bring a whole host of issues (build systems, dependency management, pointers, segfaults, etc.). I didn't want to go there, and neither did my management. But Rust feels more like a modern scripting language (except with a steeper learning curve): It has cargo to handle dependencies and builds, it works cross-platform with minimal fuss, and it provides excellent protection against shooting yourself in the foot.

It turns out that I can use Rust for CLI tools, server applications and Node.js add-ons, and it's great at all of these things.

Plus, every time I write something in Rust, my coworkers are happy, because it's fast, it's easy to install, and it's "solid."

It does take a little while to bring people up to speed on Rust. It really helps to pair program and explain what's going on when they hit a speed bump.


>Ruby and JavaScript have modern dependency managers (bundler and npm/yarn). They're easy to get working cross-platform.

Ruby is easy to get working cross-platform? The only fully supported platforms are Linux and OS X (and really only the Linux-like parts of OS X). Ruby on Windows is a trainwreck, and other platforms are not supported at all. Meanwhile C++ runs on everything and the toaster. Ruby might have many strengths but cross-platform support is not among them.


> Ruby is easy to get working cross-platform?

Compared to a C++ project which relies on a half-dozen external libraries each with their own build system? Yes, in my experience. I've spent too much of my life converting autoconf scripts to Visual C++ project files.

But Rust is definitely quite reasonable to get working on Windows, especially pure Rust, but small C extensions are OK as long as OpenSSL isn't involved. Usually I can just cross-compile or build using Travis CI.


https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/Supporte... has many more than just Linux and OS X.

(I still agree with you that it's not a super strong point.)


I think the best way to put it is that Rust has no overhead. You'll never be in a situations with Rust where you say "Oh damn I wish I had written this in X because Rust has this annoying limitation". Some examples of limitations in other languages:

* Python has a GIL (Global Interpreter Lock) so writing multi-threaded Python code is more or less impossible (there are some workarounds but it still sucks).

* Most garbage collected languages (e.g. Java, C#) suffer from 'stop the world' pauses which make doing low-latency realtime stuff a pain in the arse. For example on Android they recommend you don't create any objects in your draw functions to avoid invoking the GC. It's kind of like "Java is great, no need to worry about memory, but oh... you really have to be careful about memory here." Not all GC languages are like this, e.g. Go now has sub-millisecond pauses.

And aside from that it is safer than C/C++. So basically it will be used wherever C++ was before.

The big downside is that it is definitely one of the hardest languages to understand and write. That can make you less productive. If I were on a varied team that included less skilled people I would definitely choose Go over Rust if it were suitable - it is a lot simpler.


I hear that about Python surprisingly often, but multiprocessing avoids the GIL. It has some overhead, sure, but in my work it's worked fine.


If one just wants simplicity, why not pick C, or even Basic-80? They are simpler than Go.

For the increase in complexity, Go gives you concurrency, and Rust, safety, on top of other things.


> If one just wants simplicity, why not pick C, or even Basic-80? They are simpler than Go.

It's very simple to write bad C. Writing safe, correct C with no security vulnerabilities is surprisingly hard, which is a major reason why most OSes need to apply regular security patches.

Rust gives you speed, concurrency and memory safety at the same time. This is not free: You need to know about values, references, stacks and heaps. And you need to write your code so that objects have a single, clear owner. It also helps to be somewhat familiar with generic types and functions like `map`—some old-school C programmers might have issues with parts of Rust, but C++ programmers (or C programmers who know JavaScript) should be fine.

If you're willing to pay that price, and spend a week or two making friends with the borrow checker, Rust is a fantastic and versatile tool. But for many programmers and applications, that price may be too high.

Rust will hopefully remain simpler than C++. But it's always going to be more complex than Go or Ruby. I have no idea whether Rust will wind up simpler or more complex than modern JavaScript, though. :-)


My point exactly. Being simple is nice, but not enough.

This is why I (ironically) offered to use Basic-80, which is even simpler than Go, replying to a text that suggests to choose Go over Rust for Go's simplicity.


I use Rust primarily because it has very precise mutability semantics: things are either immutable or uniquely mutable. It is much easier to understand what can or can not happen to structures when they are passed between functions, as opposed to, say, Java, where object's lifetime is undefined.

Destruction after last use (RAII) ensures that resources such as files, sockets, db connections are closed. Also unavailable in majority of languages.

Also the language is expressive (type inference, generics, pattern matching, traits).

These three things are the main reasons, although I wish other languages (old or new) would pick up the first idea too.


When you want to go as fast as C or C++, but you don't want memory or data race errors, and you want a package manager and integrated, very easy to use build tool set.


For me it's basically the language you choose when you want to write new project, but don't want it to explode with type error / null deref at runtime and on the other hand you don't want to deal with C segfaulting and writing all the manual memory management yourself.

Also potentially if you want speed closer to C than interpreted script, and don't mind spending a bit more time up front. (offset by the time you don't spend debugging an inexplicable crash later on)


its primary application is to bring more fun into life.


Yes - and frustration. Breaking cycles with weak references and so forth. But on the plus side, once you know this stuff well modern C++ makes so much more sense.


>"But on the plus side, once you know this stuff"

Can you give some examples of "this stuff"? As a recent entrant to the world of C++ I would be curious to hear those areas I should investigate to get to that point. You mentioned breaking cycles with weak references so it is it mostly understanding how C object model is implemented and/or anything else? Any resources or book you found helpful to find that enlightenment? Cheers.


mut & being kinda like unique_ptr<> is one example.


when i taught myself golang, gobyexample.com was the best resource for learning golang! so i'm hoping this is on par with that. what gobyexample lacks is more complicated examples, but it gets you started with just enough.

rust seems like a great language, but i'd like to hear about some advantages versus golang.


If I'm not wrong, I think golang and rust target different group of developers. Rust portrayed as low-level system level lang. potential replacement for c/c++. Where golang is touted as potential replacement for high-level application which are currently being written in Python/Ruby/PHP


This is partly true, but there's a lot of crossover. I've been happily using Rust as a node.js replacement for the last few weeks.


Go is still intended to replace C/C++ in many domains, e.g. networky things. It's original goal was basically a better C.


Go has a garbage collector and therefore pauses which may or may not be acceptable to you. Rust does not (maybe you can enable GC though idk). Rust also encourages immutability. I'm gonna guess Go is more traditional there. Rust uses the LLVM compiler...not sure about Go. Rust is meant to be used where speed is absolutely critical.


There's no tracing GC in Rust, not even enable-able. The reference Go compiler doesn't use LLVM, there is https://llvm.org/svn/llvm-project/llgo/trunk/README.TXT though.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: