A few days ago, I suddenly got the urge to download Rust and have a little play around with it. Truth be told, I've been hearing some good things about it, and seeing as they now have a stable version out, I felt it was finally time to give it a little play around.
far, I've played around with it for about 3 days. There's still a lot I
don't know about the thing, but at the same time, I now have a decent
feel for some "everyday aspects" of coding in this language.
1) For me, Rust seems to occupy a weird
space between low-level C-like systems language (with the structs,
datatypes, and native binary runtimes), a traditional high level
language (e.g. Python, Java, C#) where you don't really need to worry
about resource usage, and mind-bending functional language quackery
(e.g. Haskell, Lisp).
At least for the first day, it
was mostly the third one of this set (I'm looking at you Haskell in
particular). This is due to 2-3 particular things:
just like Haskell, has a powerful "match"/"super-charged switch" type
control structure thing that works with some really powerful pattern
matching stuff. I spent most of my first day playing around with
this and really absorbing when you use it, how to use it, and getting
the feel of it. By the end of the day, I think I'd gotten at least a
basic understanding of it, and how it has come to be quite a cornerstone
of the Rust approach (due to it's unique approach to error handling,
see next point)
b) Rust, like Haskell again, has
a powerful/descriptive type system, that also has the dubious honour of
being quite confusing/intimidating to work with at times. In the case of Rust, the intimidation/confusion comes up in several ways:
i. "Options" as return types
- Rust is set up to make it quite explicit that "error conditions"
occur, and that you must handle them. One of the most visible ways is
that many API's return "Option" types (i.e. "Some(x)" and "None", or "Ok(x)" and "Err(x)"). "Some(x)" means that it returned a usable value, "None" means that the values is intentionally blank, and "Err(x)"
means an error condition occurred. As you've probably guessed by now,
these should be handled using "match". (Note: You can bypass using a
match by appending ".unwrap()" after a function call to just bypass all this stuff and get whatever value Some(x) may have (at the risk of a "panic" (aka "civilised segfault") occurring when an error condition happens).
ii. Confusion about which type of "string" to use
- Though Rust is a modern language, that doesn't mean it has "strings"
all nicely handled yet! Far from it... The problem is that there are two
typs of strings "String" and "&str".
IIRC, the first is a "String Object", and the second is "a reference to
a string slice" (string literals are included in this second category).
The confusing thing is that one is used in some places, and the other
in other places, and the reason you have to deal with both relates to
the memory management model in Rust (i.e. all the mutable vs immutable,
borrowing vs ownership, heap vs stack, etc.). While I so far haven't run
into many problems with memory management yet (touchwood... I may have
to revise that statement after playing with Box'd structs being passed
between various functions a lot more later on), string handling is a
point of confusion.
iii. Some of the function signatures look scary due to all the generics and weird stuff that goes on to satisfy them - Prime examples include all the "<'a>" stuff that you apparently need when implementing certain things.
c) Short function definitions end up being quite similar - For
instance, in Rust and Haskell, you can return from a function without
having to say "return" (though you can use return to return early; also
the "recommended form" looks quite ugly/weird IMO when you've got a
d) A lot of the API's are designed in a functional/chained format
- Instead of wrapping the value of one function within another
function, Rust API's prefer using dot-chaining. This results in weird
stuff like all the standard math-library functions existing on the
number types (e.g. (3.14 as f64).sin().abs() instead of abs(sin(3.14)) or Math.abs(Math.sin(3.14)) ). The IO libraries are even weirded as a result of all this
e) The type-inferrence stuff is both cool and slightly creepy/confusing.
It often seems to work, until it doesn't, and then the compiler tells
you all about it (though it tries to be helpful, and mostly succeeds at
f) It's cool that the string formatting
stuff largely follows that same rules laid down by Python 3's
curly-bracket style, while retaining a printf-like way of supplying the
arguments (mixed in with a bit of Py "optional named arguments" magic). - For anyone transitioning over, this is pretty handy for getting up to speed.
2) The build system/package manager tool (i.e. "cargo") is really nice. It's
great that there's a standardised system built into the whole ecosystem
that defines a standardised way of getting everything to work with
everything else. So far, I've found this thing really nice (AND it helps
you set up the initial directory, complete with all git settings too!).
Other cool things about it include the fact that it helps you build + run your projects ("cargo build" (to just build), or "cargo run" to build and run). It can also call a C-compiler to compile C-defined modules, and the builds are generally really fast.
3) The module system is really weird and confusing. -
One of the few big warts I've encountered so far is that the module
system is really hard to understand. All I want to do is just split some
functions out into two different files, and then import those back into
the main file as separate named modules. However, despite ready the
sections on this stuff multiple times, and experimenting quite a bit, I
still haven't managed to figure this out. Argh! (And looking at the way
that other Rust projects handle this is proving that this is still a
very confusing aspect that's really hard to understand, and introduces a
whole new class of confusing ways to screw you over in the future).
Rust is an interesting language, which occupies an interesting niche
with some interesting features and nice supporting tooling. However, the
language does has a whole bunch of rather scary warts/quirks that can
be a bit offputting...
Then again, we can at least be
thankful that the code written in this language is generally readable
(as opposed to *cough* *cough* Objective-C, which I absolutely cannot
manage to parse and comprehend... that language is an abomination of
confusing, unparsable syntax!)