Rust is a potentially important language with weak, and badly organized, documentation. The "30 minute introduction" starts by introducing the package manager, manifests, crates, and lock files. But it's easy. It says so, twice! "You should feel right at home if you've used tools like Bundler, npm, or pip."
Rust is the best hope we have of fixing the legacy mess at the bottom left from C/C++. I really hope the Mozilla crowd doesn't fuck up.
> The "30 minute introduction" starts by introducing the package manager, manifests, crates, and lock files. But it's easy. It says so, twice! "You should feel right at home if you've used tools like Bundler, npm, or pip."
Presumably you're implying something is not easy... But I don't understand why? That is, why does introducing the package manager (I.e. allowing people to jump straight in with nicer tooling) make the language not easy?
Because beginners generally don't care about tooling, external libraries etc. They want a compiler (and preferrably a REPL) and some language pointers and then write a program that searches for primes or whatever.
On the other hand, if you want to do something else in addition to a prime sieve than some sensible advice on tooling can save a lot of grief down the road. A couple of years ago I decided to learn Ocaml but never progressed much farther than writing a couple of Project Euler problems. Recently I decided to relearn Ocaml with the Real World Ocaml[1] book and, in retrospect, one of my favorite things about the book is their recommendations on tooling[2]. The improved REPL and the vim plugin they recommend are the sort of thing that make learning the language and its libraries much more pleasant but are also the sort of thing I wouldn't think of installing on my own if all you told me to install was the compiler (which kind of explains why I never went very far in my previous attempt at learning the language).
There are lots of tutorials; if you want to dive straight into the code look at 'rust by example'. You dont even have to install rust to use it.
Also, REPL is a cute toy, but absolutely not important and hard to do for compiled languages. If you want one, you can run your own copy of play.rust-lang.org; its actually pretty good.
I get that it's hard for compiled languages and I've learned to live without one for many of them. But "a cute toy, but absolutely not important" is way, way off the mark. When available, a REPL is a godsend and has saved me personally enormous amounts of time.
Besides, there do exist compiled languages with REPLs (Haskell, Scheme, Common Lisp). Even Python has to compile the textual code into bytecode and execute that.
I'm happy to accept 'relatively unimportant compared to other issues with rust'.
REPL is a useful learning tool, but its also a not relevant to the majority of tasks for rust. Rust is for large complex tasks. If you want a scripting language, use python.
REPLs aren't just for learning- they are great for exploring and interactively designing programs. You are also conflating REPLs with scripting languages - the parent post already has a bunch of examples of languages suited for complex tasks that make very good use of their REPLs.
play.rust-lang.org is a bite sized code compile \ execute environment that lets you play, explore, delect different compilers and share failed build to get help. Its fantastically useful ... but its not a REPL, and being one would in no way make it more useful.
REPLs are useful where a few lines can make a meaningful program.
I use the REPL to try out syntax I don't quite remember and to ascertain myself of how the libraries work, something that I have to do often. It's not an absolute necessity, but it makes coding a lot faster.
You know, that's what some people's attitude is towards high-level languages and performance: 'who needs a better C? Just write the performance-critical code in C and the rest in Python or whatever'. Just sayin'. ;) (obviously people who say this aren't working on kernels and browser engines, but still.)
On a more positive note, once it settles down a bit, Rust would be a good language to support for small ARM boards like the Arduino Due. With 96 KB RAM and 512KB code space, they're big enough that the problems of large programs start to appear. They're too small for much of an operating system, so code runs on the bare metal. The Arduino tool chain, which is really C/C++, works on the Due, but it's really intended for tiny programs. If you have a bad pointer, you're cross-debugging at the bare machine level, which is generally considered Not Fun.
So there's a near-term use case for Rust that might develop a user base.
I thought it might be useful to document my experiences starting out with Rust, in the same vein as Artyom's "Learning Racket" series (http://artyom.me/learning-racket-1), which I got a lot out of.
I really liked the style of that series when investigating Racket myself. That is, little to no preparation, and writing down everything you try, even if it doesn't work.
This is neat. I really admire the willingness to document the type of stumbling around that characterizes lots of real programming, but which you rarely see.
I think it's a bummer you can't do `LevelMap::new()` like you want, but here are two things that work that I think are improvements on your `mklevel` function:
fn mklevel() -> LevelMap { HashMap::new() }
That works because once the compiler knows that the return type is `LevelMap`, which it knows is really `HashMap<Coord, int>`, it can infer which variant of `HashMap::new` to call. Using this same idea, you can get closer to what you originally wanted without the function, like this:
let mut map: LevelMap = HashMap::new();
There are also a couple things that I think are good syntactic improvements over the `Coord{x: 0, y: 0}` syntax that you made the `xy` function to avoid. First, you can just use a tuple:
type LevelMapTuple = HashMap<(int, int), int>;
let mut maptuple: LevelMapTuple = HashMap::new();
maptuple.insert((0, 0), 0);
If you still want to give it a name, which I think is pretty nice, you can use a struct with anonymous members, like this:
Thanks a lot for sharing this. Its going to be a lot of fun to follow. Sorry to throw feature requests at you but it would be great if you could add email subscription for this series. Also, although I'm a big fan of plain-text, I believe that serving .txt files might not be best for readability.
If your blog / series is on github, I'd happily send PRs for above. Let me know :)
Thanks a lot!
Edit: If you prefer staying away from styling your blog then even writing the content in Markdown would do the trick
Hey, thanks for reading. I agree that I definitely need to improve the presentation — plain-text is a stopgap, really, so I could get something up there before I talked myself out of the whole thing :)
I'm not a regular writer and don't have a blog, so it's not as easy as hitting 'new post' but I'll be sure to get something a bit more usable up soon.
Edit: Converted to Markdown-generated HTML for now.
Yeah, that has happened before - it's too bad because is.gd is just the shortener that play.rust-lang.org has built in. I suppose I could use a different shortener myself, but I suspect they are all similarly problematic.
Rust is one of those languages I'm excited to learn, in about 2 years time.
It's a shame there are so many comparisons between Go and Rust because it does both a disservice. Rust is still very much unfinished and still being developed, but despite this it is achieving its goals of complete safety and compile time checking.
It's an admirable project, but it's got a lot of syntax fiddling and redesigning to go before I think it's 'prime time' ready.
That's disheartening, it looks like a nonsensical mix of about a dozen different languages right now. Are naming guidelines and predictable, coherent choices just not hip enough in 2014?
> Are naming guidelines and predictable, coherent choices just not hip enough in 2014?
It has been rather ad-hoc for some time now, but we have some great things that are currently landing that will clean tons of this stuff up. Mozilla employee Aaron Turon has been doing a ton of great work shepherding our libraries to a far more polished state. 1.0 is gonna be great!
The reason for using nightlies currently is that there is no stability guarantees over the releases (sometimes, your code broke just 2 days after release). So, release doesn't give you benefits, and you are learning an unreleased language, so nightly is the way to go.
I'm pretty sure a lot of libraries will move to stable once there is something that can have the name.
I'm interested in Rust also.
But I'm wondering, does it have these features, and to what extent?
* coroutines
* pure functional programming (enforcing it in an isolated subset of the code)
* laziness
* (clean) meta-programming
* introspection
* garbage collection
Especially for stream-based programming (essential for internet applications), you'd need something like coroutines, to keep things manageable. I'm wondering if there is example code for a well-performing Rust-based web-server out there already.
Also, how does Rust compare to other modern languages, like Scala?
I've followed rust for a couple years, never done anything major with it.
It doesn't have coroutines, but they might be possible to implement using a macro that expands to an iterator implementation. It also has go-like channels and can be used with both N:M (green) threads and os-native 1:1 threads. It handles streams of data quite well.
All variables and references in Rust are immutable by default and accessing global mutable state can only be done in explicitly marked unsafe code. I think that all functions that have no "mut" or "unsafe" in their signature are pure unless they use an "unsafe" block. Exposing unsafe behavior in a safe interface is a contract violation and considered a bug.
There's no built in laziness, but because Rust allows for overloading the pointer dereference operator, it should be possible to implement thunks in a syntactically transparent way.
Rust has a full macro system. There are AST-based macros for when you need a serious amount of power, but most macros are implemented on a partially structured token stream and are implemented a lot like scheme's syntax-case. It's a lot cleaner than metaprogramming in dynamic languages like python and ruby IMO, and it has no runtime overhead.
Introspection? No, but I think doing something about that is on the post-1.0 roadmap.
Garbage collection. A reference-counted pointer like C++11's shared_ptr is available in the standard library. Its use is discouraged, though, in favor of structuring your code to work with Rust's lifetime system and borrow checker. Doing that, your memory is managed in a safe way with no runtime overhead, you don't have to worry about reference cycles, and the structure it enforces in your code tends to be easier to follow (although often harder to come up with).
Rust isn't really meant to be used in place of Scala, it's more of a "competitor" to C++. It keeps C++'s promise of "don't pay for what you don't use" and strongly favors 0-cost abstractions. It differentiates itself from C++ by using more modern abstractions and garaunteeing safety.
I'm not totally clear on the status of green threads but I'm not sure they should be encouraged presently. My understanding is they will be an external package and std::io will not support them.
I think that the external package is supposed to work by providing an alternate implementation of std::io and several other parts of the standard library. Green threads will continue to be fully supported, but they are second class.
Would not be part of the core language. Quite feasible as a package or plugin, and I think under development.
> pure functional programming (enforcing it in an isolated subset of the code)
I don't believe Rust has a way to enforce purity of a function, but the type system could certainly help you out. Rust's type system doesn't have HKTs, so you can't do generic monads, but you could probably do an IO-specific monad.
>laziness
Already implemented in the loose sense with e.g. lazy iterators. Thunk-based evaluation could be implemented using operator overloading, lazy data structures, etc.
>(clean) meta-programming
I haven't used it a whole lot, but Rust's macro system seems clean and powerful.
>introspection
Could you expand a bit on what you mean by this? The kind of introspection I'm thinking of seems to run counter to the strict correctness and isolation principles espoused by Rust.
>garbage collection
This would be implemented using a custom wrapper type. I think it's in the works. However, the need for GC is mostly obviated by the statically checked lifetime system.
>Also, how does Rust compare to other modern languages, like Scala?
Rust, obviously, does not run on the JVM.
The type system is similarly rich, although (being HM-based) does not support subtyping (instead opting for the interface/typeclass/trait-only approach used by e.g. Haskell). The most obvious improvement over Scala is substantially improved type inference.
Scala absolutely requires heavy heap usage and GC. Rust requires no heap usage and absolutely no GC. All that stuff is determined at compile time, which is great for determinism and performance. The downside is that it requires some more effort from the programmer to think about lifetimes and such.
Rust has a substantially lower-level focus than languages like Scala or Haskell. It aims to be a C(++) replacement, not a Java replacement.
I can comment on a few of those. I'm not a Rust expert but I've been following it some. One thing I don't know about are coroutines. I know Rust has iterators but I don't think those are quite the same?
Rust uses lazy iterators for everything (including functions like map, and datatypes like arrays).
Rust actually has a full hygenic macro system (metaprogramming). IMO this is one of the coolest things about Rust.
Rust does not use reference-counting GC. Instead, it has a fairly unique borrow checking system for garbage collection, which not only handles cleaning up memory but also cleaning open sockets, file handlers, etc. There is a generic type implementation of a reference counted variable you can use, though.
Although there are no "pure functional" (i.e. side-effect free) guarantees in Rust AFAIK, the borrow checker actually catches a lot of issues that pure functional programming is meant to fix (i.e. issues from unexpected state mutation). Also, variables in Rust are immutable by default, you have to use the "mut" keyword to make them mutable.
>Rust does not use reference-counting GC. Instead, it has a fairly unique borrow checking system for garbage collection, which not only handles cleaning up memory but also cleaning open sockets, file handlers, etc. There is a generic type implementation of a reference counted variable you can use, though.
Rust does not have garbage collection, unless you count using types like Rc<T> garbage collection. It has destructors, just like C++, and uses them for cleaning up resources like file handles and memory. Lifetimes do not really interact with destructors. They're part of the type system and do not have any effect on the generated code.
I was given the impression that as soon as the lifeline was out of scope, the resources were cleaned.
So it acts like a GCed language in most ways, in that, most of the time you don't have to worry about cleaning up after yourself, and that it is hard to memory leak.
> I was given the impression that as soon as the lifeline was out of scope, the resources were cleaned.
And you are right.
Even though GP is almost right, lifetimes do interact with destructors and, in fact, dictate when values go out of scope and hence when drop (Rust destructor) is called.
> So it acts like a GCed language in most ways, in that, most of the time you don't have to worry about cleaning up after yourself
Nope. In Rust you almost always have to worry about cleaning up although not explicitly, but via lifetime and ownership management.
Sometimes you're lucky and those lifetimes can be inferred, but you still have to think about ownership a lot.
> and that it is hard to memory leak.
But not in the same way.
In Rust you can memory leak only when:
- Using GC like Arc or Rc with cycles.
- On blocks/fns marked as unsafe (which are mandatory for unsafe operations).
Sorry, I meant (and I guess myrryr meant too) ownership instead of lifetimes. I was in the process of ninja-editing my comment when I saw your reply :P
Ownership dictates when a value goes out of scope and hence when the destructor is called.
If you'd like to call that a form of GC, I'd prefer to call it "compile-time GC" to avoid confusion. I rarely see "garbage collection" being used to refer to anything other than dynamic garbage collection.
I assume coroutines could be trivially implemented with sync_channel(0), which blocks the sender until the receiver takes the message. (And the receiver blocks until there is a message available.)
The 'generator' use of coroutines could be implemented with Receiver::iter, which is an iterator over a channel.
(As I understand it, coroutines are just a special case of channels.)
I love it! I'm sure everyone has had the idea of logging all their troubles but this guy has the fortitude to do it. It is troubling to see that Rust still has a way to go in terms of learning it. I was trying to learn Rust 1.5 years ago and ran into enough problems that I gave up. I will try again this week...
> It is troubling to see that Rust still has a way to go in terms of learning it.
Have no fear! Rust 1.0 is not far off and things will become a lot better then. Just be aware that google is pretty useless with now. You are much better off with the official docs:
I understand the frustration while learning Rust. As a Rust beginner myself, I suggest not to follow guides (except the ones at http://www.rust-lang.org) since most are outdated.
My way to learn Rust: I spend 90% of the time in Rust's std docs, 9% of the time watching the type/borrow checker spit at me and 1% of the time in IRC when I'm out of ideas. Google was useless most of the time.
I found revisiting http://doc.rust-lang.org/index.html (specially the spec and the first four guides) as I was getting insights from experimenting with the language allowed to me to understand what was going on.
For me the most important parts to understand to get up to speed were:
- The module system (understanding `mod` vs. `use`).
- The concept of ownership. References. Lifetimes. Borrows. Copies. Moves. Moving out of. `ref` on tuples.
- Getting used to rustdoc's format. Docs hard to navigate not knowing the language idiosyncrasies. Trait methods are often not textually documented.
- Understanding rustc error reporting. IMHO the worst part of rustc is the cryptic lifetime error messages (though they're getting better).
Good luck in your journey! I'm loving Rust so far.
---
Some concrete feedback:
> This probably isn't right (I'm sure empty maps must be supported somehow), but it will do for now.
Indeed. I see you joined the IRC (great idea, people are very helpful there) and got the correct syntax, but to fix the ~str problem, the answer is:
let mut map = HashMap::<Box<str>, uint>::new();
Box<A> is the resulting type of an expression "box a" where `a` is of type A. I guess an example will make it more clear:
let owned_three: Box<uint> = box 3;
But Box<str> is actually just String, so the final line should read:
let mut map = HashMap::<String, uint>::new();
By the way... why do you need to box the str?
Local bindings (args, let bindings...) are allocated on the stack and must have a fixed size. A string has variable size by definition, but a box has a known size (the size of a reference to your heap-allocated str), so can can wrap the str (on the heap) on a box (on the stack).
That's why you'll never see a sole `str` as a type: you either see the (boxed) String or &str (reference to a string slice).
> Ah, OK. So I think this means Rust has no built-in way of determining if two int-arrays are equal.
Actually the problem is Rust currently doesn't implement traits for static-sized arrays because the size is part of the type signature and each size would need to be implemented separately.
> As a Rust beginner myself, I suggest not to follow guides (except the ones at http://www.rust-lang.org) since most are outdated.
Nearly every (external) guide I've tried to use is out of date, but it's impossible to tell from the guide itself. I wish everyone who wrote a guide or tutorial would label it with the version of Rust it works with (or at least the date).
> Actually the problem is Rust currently doesn't implement traits for static-sized arrays because the size is part of the type signature and each size would need to be implemented separately.
But it does implement those traits for slices. So this isn't typically a problem in practice because you can coerce fixed-length arrays to slices and then compare those.
This was really helpful - it's nice to see that he takes the same steps that I do when learning a new language.
Interestingly, this highlights just how incredibly important up-to-date and authoritative tutorials/docs just are. Julia/scikit-learn are both absolutely amazing in this regard.
There is syntax highlighting for Vim, Emacs, and Sublime Text editors. GitHub does syntax highlighting for Rust repos and in Gists. There is support for Rust in the Pygments syntax highlighting library (which is used by a bunch of tools). Several community members have worked on IDE support for various IDEs (e.g., http://plugins.jetbrains.com/plugin/7438). Post-1.0 (i.e., next year) we plan to really focus on excellent tool support, so expect lots of cool stuff then.
Oh. I thought you were objecting to the syntax highlighting. I'm inclined to agree with you about refactoring tools. Even so, I bet you that if Rust takes off, they will eventually exist.
Some languages should, some should not. Probably a language like Rust should. Asking that of Smalltalk may be too much. But every language can benefit from tooling, even if it doesn't need it. For instance, do you know of any language more 15 years old that still has no debugger, let alone syntax highlighting?
ReSharper and a debugger a completely different pair of shoes. I agree that you need a debugger (and Rust has one, gdb), but I don't agree that it needs refactoring tools.
Good IDE support is the difference between constantly hunting through (or thoroughly memorizing) documentation and having a program help you with it. It's also great for merging the edit and compile steps so that you can correct errors in realtime. Refactoring tools are nice but are not the strongest selling point for IDEs.
I recall reading old Rust design considerations where they decided to stick with the `foo.bar` syntax because they like the way IDEs can autocomplete with documentation after typing the dot.
I think a good refactring tool is one of the best ways to improve productivity for a developer. When you have a strongly statically typed language it should be possible to create really good refactoring tools. They don't have to exist at the same time 1.0 is released but I think they should be created as soon as possible after that point.
Rust is the best hope we have of fixing the legacy mess at the bottom left from C/C++. I really hope the Mozilla crowd doesn't fuck up.