Sunday, January 8, 2017

First Steps with Rust

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.

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

First Impressions:
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:
    a) Rust, 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 multiline function)

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

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

Overall impressions:
Overall, 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!)

No comments:

Post a Comment