Rust For PHP Developers (Part 1)
I have recently started to dive into Rust for a project I am working on and just wanted to point out some of the interesting aspects of Rust which I haven’t seen in PHP and even in some of the other languages I have worked with.
I will keep updating this as I learn more about Rust but here some of the pointers:
1. Compilation
Right off the bat, the first thing to know about Rust compared to PHP is that Rust is a compiled language. This means that you need to compile your code before you can run it.
fn main() { println!("Hello, world!"); }
To compile this code you need to run the following command:
rustc main.rs
This will create an executable file which you can run.
./main
This is a big difference from PHP where you can just run the code without any compilation. However for development purposes you can use cargo
which is a package manager for Rust and it will take care of the compilation for you.
cargo run
2. Package Manager
As mentioned above, Rust has a package manager called cargo
. Consider it as composer
for PHP. You can use it to create new projects, build and run your code, and also to manage dependencies.
While just testing out some code I really like to use watch
command to keep compiling my code as I make changes.
cargo watch -x run
You have to install cargo-watch
to use this command.
3. let variables
You might have seen this before, as every PHP developer works with JS, so yes just like Javascript you need to define variables with let before them.
e.g.
fn main() { let my_variable = 9; }
You might be noticing type inference happening here similar to PHP. However, Rust needs a type for every variable, so it is guessing the type for you and unlike PHP you can’t really change the type of the variable later e.g.
fn main() { let my_variable = 9; my_variable = "Hello"; // This will throw an error }
4. Printing variables
In PHP you can print almost anything regardless of the type of the variable. Very handy for quick debugging. However, in Rust this work a bit differently. Firstly, you can’t print every type of variable using println!
macro.
The type of the variable should implement the Display
trait or a Debug
trait.
To give you an example
fn main() { let my_variable = 9; println!("{}", my_variable); // This will work } #[derive(Debug)] struct MyStruct { name: String } fn main() { let my_variable = MyStruct { name: "Hello".to_string() }; println!("{:?}", my_variable); // This will work }
it wouldn’t have worked if we didn’t implement the Debug
trait for MyStruct
. More on traits later.
5. Mutability
Another thing which was very new to me was the concept of mutability. In Rust, by default, variables are immutable. Which mean you can’t do something like
fn main() { let my_variable = 9; my_variable = 10; // This will throw an error }
and you will need to specifically tell Rust
fn main() { let mut my_variable = 9; my_variable = 10; // This will work }
This is similar to how a const
would work in PHP, however const is something I would usually use for defining constant data and not for variables.
6. Shadowing
Again very different from any other language I have worked with. The concept is that you can define a variable with the same name as another variable in the same scope but instead of changing the value of the variable you are creating a new variable.
fn main() { let my_variable = 9; let my_variable = my_variable + 1; println!("{}", my_variable); // This will print 10 }
In the above example the second my_variable
is a new variable and not the same as the first one. A better example would be where we have a separate block
fn main() { let my_variable = 9; { let my_variable = my_variable + 1; println!("{}", my_variable); // This will print 10 } println!("{}", my_variable); // This will print 9 }
This is a good segue into the concept of scopes in Rust.
7. Scopes
In PHP we have the concept of scopes as well, however, in Rust, it is a bit more strict. Also you can’t just start a new scope anywhere you want in PHP. As you saw in the previous example, you can create a block of code using {}
and any variable defined inside that block will only be available inside that block. Another example to illustrate this
fn main() { { let my_variable = 1; println!("{}", my_variable); } println!("{}", my_variable); // This will throw an error (my_variable not found in this scope) }
Since we are already talking about blocks, one thing that was simply pretty “crazy” to me was in any scope if the last line is a statement that doesn’t end with a semicolon, it will return the value of that statement.
fn main() { let my_variable = { let my_variable = 1; my_variable + 1 }; println!("{}", my_variable); // This will print 2 }
See? and this just doesn’t apply to blocks, it applies to functions as well. So don’t be startled if you see a function without a return statement.
8. Memory Management (Ownership and Borrowing)
This is a big one. With PHP I would never have to worry about memory management. However, in Rust, you have to think about it. Not crazy like C or C++ but still you have to think about it. It is important to understand references and pointers in Rust. It also has a concept of ownership and borrowing which is very unique to Rust. Let’s take a look at a few examples which explains the ownership and borrowing in Rust.
fn print_string(my_variable: String) { println!("{}", my_variable); } fn main() { let my_str = String::from("Hello, World!"); print_string(my_str); println!("{}", my_str); // This will lead to an error }
Now, if you are a PHP dev the above code will look perfectly fine to you. However, in Rust, this leads to an error because we are passing the ownership of my_str
to the print_string
function. Which means my_str
is no longer available in the main
function. This is where references come in.
fn print_string(my_variable: &String) { println!("{}", my_variable); } fn main() { let my_str = String::from("Hello, World!"); print_string(&my_str); println!("{}", my_str); // This will work }
Here we are saying we don’t want to pass the ownership of my_str
to the print_string
function, we are just passing the reference, i.e. print_string would just borrow the value of my_str
and not own it.
One thing to remember though would be a borrowed value can’t be changed. To do that you would need to pass a mutable reference.
fn change_string(my_variable: &mut String) { my_variable.push_str(", World!"); } fn main() { let mut my_str = String::from("Hello"); change_string(&mut my_str); println!("{}", my_str); // This will print Hello, World! }
If you try the above without the mut
keyword you will get an error.
My original intention was to write everything that I learnt which is different from PHP however there are plenty of more things to cover. I will write about them in part 2.