Rust for Java developers, Introduction

rusty bridge

Manifesto

ahum..

It is my conviction that rust should should rule the world! Not only because it is cool new technology, but first and foremost because it requires less computing resources (cpu cycles and memory) which also translates to natural resources (electricity). Since large parts of the grid are still powered by fossile, rust in the end is a way to cut back on CO2 emissions! Especially compared to java which is still resource hungry, despites lots and lots of improvements over the years.

And java powers much of the web. Rust, while being regarded as a systems language is a perfectly feasible choice for any web application. Yet it’s uptake is slow, in part because of the learning curve. This blog is intended to help developers make the change. There is already splendid documentation out there. I hope that I can add some information that I have found useful, once I got my head around it.

tl;dr Do not think ‘Rust is just a new language, I can map what I already know onto some new keywords’. Rust is different. But taking the effort will pay off.

In this post you will see some syntax and get a feel for how rust differs from other languages because of its implementation of ownership.

Imagine a language in which duplicating a valid line of code slaps you in the face with a compilation error:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    let name = String::from("Bob");
    let msg = get_hello(name);
    println!("{}", msg);

    // let msg2 = get_hello(name); -- ERROR Use of moved value
    // println!("{}", msg2);
}

fn get_hello(name: String) -> String{
    let mut text = "Hello ".to_owned();
    text.push_str(&name);
    return text;
}

line 6 is a copy of line 3. The first compiles but the second does not. WTF?

So what is going on? Let’s look at it line by line.

Starting at line 1 it seems easy enough, and it is: fn defines a function, in this case main which is the entrypoint for any rust application. We could add pub to make it public, but in this case that wouldn’t be necessary. Next (), like in a lot of languages means: put any parameters here. Curly braces: we have block scope.

let defines a variable, in ths case name. Some things to say about this: For one: rust is strongly typed, so we could also say let name: String = String::from("...") but rust infers the type as much as possible (and that is much more than let’s say java does).

Actually let does more than declare and assign, it establishes ownership. More on that later.

One last thing to mention here: this is allowed:

let name;
name = String::from("<your name here>");
let msg = get_hello(name);
and this is not:
let name;
// let msg = get_hello(name); -- ERROR Use of possibly uninitialized variable 

because no value is actually assigned to name. In java we could cheat and assign null to name, but rust won’t have any of that.

There is no null in rust.

This is when you say: ‘Thank god for that!'

To which I would reply: ‘Welll… actually there is’, but that is an advanced topic, and beyond the scope of this post. Let’s just stick to the good habit of never having null-references and life is fine.

So much going on in just 2 lines of code….

About strings: There is String and there is &str (pronounced string-slice). Javans are allowed to apply the following mapping in their head:

Rust Java
String StringBuilder
&str String

So rust String is mutable and &str is not. The latter is also a (shared) reference. This is important. It means I have borrowed something, I can use it, but I am not allowed to change it and I am certainly not the owner. More on ownership later…

On to the next two lines.

3
4
let msg = get_hello(name);
println!("{}", msg);

Nothing too fancy: we call a function and print the result. Rust has easy string interpolation using {} for a value that you want to insert into a string. The print! statement ends with an exclamation mark and this signifies that you’re dealing with a macro. So it compiles to something completely different, but who cares about that?

Next:

10
fn get_hello(name: String) -> String {
This function actually has a return value which unlike java and C is at the end of the declaration (following the arrow->). I find this more intuitive.

11
let mut text = "Hello ".to_owned();

Here we create a String, concatenate the input argument and return the result. Make sure the test variable is mut. The method to_owned creates a copy of a str slice in the form of an owned String.

The mut keyword indicates that we are actually allowed to change the the value of some_string. I mentioned earlier that String is mutable, but if you actually want to mutate it, you have to add mut. And: values aren’t mutable (or not), bindings are!

So:

let x = String::from("x");
//x.push_str("NO!"); // compilation error, x is not mutable
let mut y = x;
y.push_str("YES!"); // but this compiles!

Weird, right? Well remember that by default nothing is mutable. And that things can magically ‘become’ mutable through mut.

But be aware: What do you think would happen if you’d add get_hello(x);? It would not compile. Ownership has passed from x to y. Now, sit back and just let this sink into your mind…

Still here? After y=x, x can no longer be accessed!

So in this case, there is never a situation in which a value is mutable and immutable at the same time. The compiler won’t have it. This way it prevents a lot of bugs. The rust compiler is quite famous for handing you friendly reminders that your code has issues, even suggesting the fix…

Next line:

12
    text.push_str(&name);

What’s interesting here is that we pass a reference to name, using the ampersand &. A reference to a String is by definition a string-slice. So the signature for push_str is:

pub fn push_str(&mut self, string: &str)`

Oh and this is interesting: the self keyword is sort of like this in java, but not quite. See this is the reference to the object you act on in java. In rust, like python, if we have a function on an object (also called a method), the first argument is always self.

There are objects in rust, but they’re called struct, and they are similar in some respects, but there’s no inheritance. Remember: favour composition over inheritance.

Okay, one more line to go:

13
return text;

You might have noticed that this could have been java code, except that rust prefers snake_case for local variables. The compiler actually enforces this. You can turn it off, but that would be rude. The funny thing about this line is that in practice it will always be written like this:

13
text

return is optional (handy for early returns), but it needs the semicolon. It’s shorter without them, so why not?

Be aware that is more powerful than it seems at first glance. The last line(s) of anything, provided that is does not end with ; is always evaluated as an expression and returned to the caller. So it could have been a compound match expression (fetaured in a later blog) or a simple if (which is also an expression!):

fn foo(bar: bool) -> u32{
    if bar {
        1
    } else {
        0
    }
}

This is so much cleaner than anything else!

ownership

I saved the best for last here, but I have already given some hints as to what this is. Please memorize the following rules:

  1. There is one, and only one owner of a value
  2. Ownership is scoped by blocks: at the end of the containing block, a value is dropped.
  3. I may lend shared references to any number of any number of borrowers.
  4. I may at any given time lend only one unique reference to a borrower. I remain owner.
  5. I may pass on ownership, but then I can no longer use it in any way.
  6. Reassigning a value to a new binding means transfer of ownership.
  7. A function parameter becomes owner when the function is called. (unless it’s a reference)

The latter is the reason that I could not call get_hello twice with name as the argument. At the first call, my ownership is passed on. The function, being the owner, can do with as it sees fit, but at the end of the function, the the value is dropped and you can no longer use it. Once you have updated your mental models with this knowledge, your life as a rust developer has become a little easier.

Astute readers may have noticed the &mut before self in the function declaration for push_str. This is what a unique reference looks like. So you get a mutable reference to self, which means that you can mutate self (here append some text to the string), but you have not become the owner. The mutable reference to self goes out out scope once the method ends and the original owner can carry on using it. This is true for any &mut.

If you have made it this far, I recommend you start reading the book and install rustup. Enjoy!