From Java to Rust

Relevant Post: Initial Steps with Rust

Relevant changes to adapt to Rust, coming from a Java background. Contains information about Rust’s environment and the language structures.

For code snippets on how to do specific things as we do in Java, check my Rust experience post

Rust Environment

Cargo

Cargo is Rust’s dependencies manager and most projects should be created and managed with it, for easier use.
Commands I used so far:

cargo new hello_cargo creates a new project.
Cargo.toml is a file which cargo creates and it contains the project’s metainformation and its dependencies.

cargo build builds binaries from source code.
cargo run directly compiles and starts the programm.

cargo install --path . installs from binaries to your system. Useful to use and test my own programs.
--force needed if it already was installed

Dependencies

Add new dependency

Just open the file Cargo.toml and add the name under [Dependencies]. This adds Randoms v0.3.14 into the build.

[dependencies]
rand = "0.3.14"

What is Ownership

In Rust memory is managed through a system of ownership with a set of rules that the compiler checks at compile time. It addresses keeping track of which parts of code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so the user doesn’t run out of space.

This is one of Rust’s central features and takes some time to get used to.

Stack vs Heap

They are both parts of memory that are available to your code to use at runtime, but they are structured in different ways.

stack it stores values as LIFO (Last in, First Out). All data stored here must have a known, fixed size. Think of it as a stack of plates, where removing plates from the middle of the stack wouldn’t work as well as adding and removing from the top. This is called pushing onto or popping the stack

heap it contains values with an uknown size at compile time, or a size which may change. The heap is less organized as the stack. When you put data into the heap, you request a certain amount of space. The Operative System finds an empty spot on the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This is called allocating on the heap. Because this pointer has a fixed, known size, you can store the pointer on the stack, but when you want the actual data, you must follow the pointer.

Pushing values onto the stack is not considered allocating. Pushing to the stack is faster than allocating on the heap, because the OS never has to search for a place to store new data; that location is always at the top of the stack. Accessing data from the heap is also slower because you have to follow a pointer to get there.

When your code calls a function, the values passed into this function and the function’s local variables get pushed onto the stack. When the function is over, they get popped from the stack.

Language Structures

Variables

Create a variable

In Rust, variables are inmutable by default. mut allows them to be mutable.
Several words-variables are separated by underscore.

// new() is a static method from String
let your_guess: String = String::new();
let mut another_guess: String = String::new();

Shadowing a variable

This is often used to reuse the name of a variable, when we want to change its type without needing to have 2 separated variables, one as my_var and another as my_var_str.

let x = 5;  
let x = x + 1;
println!("the value of x is: {}", x); // 6

References

(Check code! Change it to a simpler example).

& indicates that this argument is a reference. This is a way to access a piece of data, without needing to copy it into memory multiple times. Like variables, references are inmutable by default. To do it mutable we’d to write &mut guess.

let mut guess: String = String::new();
io::stdin().read_line(&mut guess)
  .expect("bla");

Constants

They are declared with const instead of let. They have to be a literal and may not be the result of a function.

const MAX_POINTS: u32 = 100_000;

The main difference of mutables vs shadowing is with the former, we may change the variable type, as we’re creating a new one with the keyword let.

Functions

Template

Is it possible to return sooner than at the end of the function with the keyword return, most functions should return the last expression implicitly though.

fn main() {
  let x = plus_one(5);
  // do something with x
}

fn plus_one(x: i32) -> i32 {
  x + 1 // this is an statement
}

Statements vs Expressions

Statements are instructions that perform some action and do not return a value.
Expressions evaluate to a resulting value. An example of this is the block we use to create new scopes {}.

fn main() {
  let x = 3; // this is an statement.  

  // this whole block between {} is an expression
  let y = {
    let x = 6; // this is valid only at this scope
    x + 2 // notice there is no semicolon
  }

  println!("x is {} and y is {}", x, y);
  // will print the values 3 and 8
}

Control Structures

if as a let statement

Because if is an expression, we can use it to the right of a statement.

let condition = true;  
// both have to be of the same type.
let number = if condition {
  5
} else {
  6
};

loop

This creates an infinite loop, which stops with break. It’s also possible to directly return a value for a statement. Rust also has a while loop.

let mut counter = 0;
let result = loop {
  counter += 1;

  if counter == 10 {
    break counter * 2;
  }
};
println!("The result is {}", result); // 20

for .. in

for number in (1..4) {
  println!("{}", number);
}

Error Handling

Crash on Error

let mut guess: String = String::new();
io::stdin()
    .read_line(&mut guess)
    .expect("oh no!");

Handling an error

Switching from expect to a match expression is generally how an error is handled.

The method .parse() returns a Result type, which is an Enum with the variants Ok and Err. We use match to compare against this Result.

loop {
  // ...
  let guess: u32 = match guess.trim()
    .parse() {
      Ok(num) => num,
      Err(_) => continue,
    }
}

The underscore _ here is a catchall value. In this example, it means we want to catch all Err values, no matter the information inside them.

Original Reference(s)

(I’m slowly following all chapters here + own learn by doing)
https://doc.rust-lang.org/book/ch01-00-getting-started.html