Learning Rust
foon.uk
foon.uk
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.
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?
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.
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.
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.
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.)
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.
That is not the case in rust.
[1] http://realworldocaml.org/ [2] https://github.com/realworldocaml/book/wiki/Installation-Ins...
So there's a near-term use case for Rust that might develop a user base.
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.
Hope someone finds it useful.
[0]: http://caskroom.io/
alias rustup='brew cask fetch --force rust cargo && brew cask install --force rust cargo && git -C ~/.vim/bundle/rust.vim pull'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
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.
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: struct Coord(int, int);
let mut map: LevelMap = HashMap::new();
map.insert(Coord(0, 0), 0);
Here's a rust playpen link illustrating some of these things: http://is.gd/latFCh. Enjoy!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.
I don't even mean it insultingly, there's nothing wrong with instability. I'm a big Perl 6 fan too.
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.
We aren't doing much syntax fiddling and redesigning anymore. 1.0, which is very soon, will have a stability promise and will be backwards compatible.
You can browse around that repository for many other guidelines designed to enforce predictable, coherent choices.
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!
For example: http://www.reddit.com/r/rust/comments/2kvssl/collections_ref...
* 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?
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 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.
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.
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).
- Running into a Rust bug.
It's actually the other way around: scoping dictates when destructors are called and the length of the lifetimes of borrowed pointers etc.
That is, the main factor is scoping, and lifetimes just reify that into something the programmer and compiler can reason about, to ensure safety.
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.
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.
[1]https://github.com/rust-lang/meeting-minutes/blob/master/wor...
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.)
[Edit: see <http://doc.rust-lang.org/std/comm/> for channel docs]
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.
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:
- http://doc.rust-lang.org/guide.html
- http://doc.rust-lang.org/std/index.html
Also, be sure to use `irc.mozilla.org #rust`. We are very friendly and would be glad to help you out if you are stuck.
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.
https://github.com/rust-lang/rust/issues/7622
Though the Coord solution you implemented is in my opinion better for a typed language.
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.
Anyways, most of the time you're better off implementing a concrete type like he did.
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).
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.
I completely understand author's frustration. Modern language is done once there is something like ReSharper for it. Rust is far from being done.
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.
Personally, I prefer Xerox PARC vision of programmers tools (GUI + IDE + Live Coding).