From PHP to Rust: Part II — 25 Differences With PHP

A strictly typed language like Rust as a lot of differences.

Italo Baeza Cabrera
The Startup
25 min readOct 19, 2020

--

This is a series of blog post where I document my transition from PHP, an interpreted language, to Rust, a system programming language, while exploring some paradigm differences between both as a long-standing PHP developer.

Articles in this series:

When entering the Rust world you will instantly know that is not some quick and basic language. It really is different from dynamically typed languages which are very easy to pick up and program for. Rust wants everything declared, everything “typed”, everything in its place. No room for guessing, unless it’s redundant to do so.

These are the first differences I picked first up when dealing with a small project. Of course there must some others, but I think this list is the very first you will come to find when changing a codebase from PHP to Rust.

1. Rust is explicit

One of the advantages of PHP is its dynamic language. Functions and methods can receive anything, and return anything. Variables can even be null one second and a full complex object the next. Typed properties, returns and arguments just came a few years ago.

Dynamism poses a problem, since you will have to invade some of your code with “what if it doesn’t return what I expect”.

Rust doesn’t like this. Since it tightly manages memory, everything must be statistically declared: functions must say what to receive, how these are received, and what they returning. There are no exceptions.

Let’s say we want to take "10" as a integer, as you would in PHP. In Rust, that doesn’t work. What you know of Type Juggling is non-existent in Rust.

fn sum_ten(number: usize) -> usize {
number + 10
}
sum_ten("10"); <-- This will error

Some goes for classes that can be casted to string inside strings, automatically, something that is something very convenient for some developers.

class Stringable {
public function _toString() {
return 'something';
}
}
$string = new Stringable;echo 'This is ' . string;

There are certain expressions to overturn the latter, but Rust forces you to state what you want, instead of figuring out the multiple possibilities of a variable, or playing the guess game. The only exception to the expliciteness is the return , which is magically understood if the last line value doesn’t have a semicolon:

fn something() -> String {
String::from("Hello world")
}

Well, having to figure out less from your code, means that you will be less prone to unwanted behavior, like receiving something you shouldn’t, and returned something nobody expects — the last one is aimed to those who create crates — more on that waaay later.

2. The documentation is awesome

The documentation on PHP is pretty much like opening an old book from first day at school. It’s full of pre-requisites and you will need to setup everything but the kitchen sink. It even tells you to use the mailing list to handle any installation problem, right in 1995's face.

On the other hand, Rust documentation is awesome:

  • Not because installing this is just an installer away (and Visual Studio C++ Build tools if you plan to develop on Windows). It even works on WSL2.
  • Not because the first chapter is just a few minutes for your first “Hello world!”, which to me is the very first step to learn a programming language.
  • Not because the second chapter you can have more complex program running, which is a natural progression.
  • Not because useful concepts are added as you progress, as a good introductory books should be.

It’s because every piece of code is runnable from the browser, powered by the Rust playground online.

When your programming language has a playground you can run in the browser, without having to look for it or install anything, it’s an instant 11 of out 10. The Rust books uses this playground extensively for its examples, making it easier to understand than PHP, without considering the amount of concepts to grasp.

3. Variables are not variables (by default)

Rust concept of variables is very different from PHP. While a simple variable declaration can suffice, like:

$deal = "I changed the deal.";$variable = "Pray I don't change it further.";

…you will soon learn that in Rust variables are basically the pointer in memory of something, with a name. That variable is not mutable by default.

let deal = String::from("deal");deal.pushstr(" has changed!"); // <<--- This won't compile.

And that’s kind of good. You can say a value CAN change by using let mut, so the above stops being invalid:

let mut variable: i32 = 10;variable += 10;println!("The number is {}", variable);

If you want to “reuse” a variable name, you can do it by “moving” a value to another variable.

let foo = String::from("foo"); // Declare "foo" as a String.
let bar = foo; // Now, [foo] contents has been moved to [bar].
let foo = false; // We can reuse [foo] for another kind of variable.

The rule of thumb is: everything that has a known size at compile time will be copied, like str or i32, since copying is cheap when is on the Stack, while everything that goes to the Heap (more on that later) will be “moved” (also more on that later) because it’s not cheap to allocate the memory and copy the data. It takes some cycles to find another space in memory and put the contents there.

If variables are handled like constants… what about constant and static variables? I was just gonna write about that.

4. Static and Constant live forever

In PHP, when you declare a Static or Constant, these variables lives as long as the Request is handled. Since each time a Request boots the application anew, there is no problem thinking about a static variable that will not be changed by another Request.

public static $integer = 10;

In Rust, Static and Constant values live for the entire of the application lifetime, not the request or input handled. That means, until someone shutdown the computer or the cat presses CTRL + C by accident.

static LEVELS: u32 = 0;

In Rust, reading a static variable is not problem, but it will protect you from changing it. If you plan to do so, you will have to make some arrangements to avoid data races because a thread decided it was cool to modify it when another was just trying to read it.

5. Constants CAN be expressions

In PHP, a Constant can’t be an expression, but a hardcoded value. So we can’t make an anonymous function a constant, or an if block.

public const THIS_IS_A_FUNCTION = function ($int) {
$int + 10
}; <--- This won't work.

But in Rust, we can do it with constants and static variables as long we know their size at compile time. The only requisite for constants and static variables to exist is to declare the type.

const DOUBLE: fn(i32) -> i32 = |x: i32| -> i32 {
x * 2
};
fn triple(x: i32) -> i32 {
DOUBLE(x) + x
}

Yes, as you read: I used a closure as a constant, because its size is hardcoded into the application. Well, is just for show as I haven’t check why I would use this instead of just pointing to a function itself, but allows some experimentation.

Closures are not the only thing you can use in constants. You can also use loops, if and match. There a nice section in the documentation where you can find what you can put inside a constant.

6. Bye Garbage Collector. Hello, ownership.

Garbage collector, what’s that? Well, as the name implies, is basically a system logic that runs behind the scenes that cleans up unused data from memory, like a list values after it stops being referenced by the ongoing logic.

In PHP, the Garbage Collection is mostly a “reference counter”. When a reference to a variable reaches zero, it is deallocated. It’s very simple.

When a Garbage Collection doesn’t exists, the task of deallocating memory is up to you, and if you forget about it, like it may happen in C++, you could “leak” memory into oblivion since it’s never disposed, and subsequent usages infest the memory. Same if you deallocate something twice, you end up deleting something you shouldn’t.

Because Rust doesn’t have a Garbage Collection system, and doesn’t want you to deal with having unsecure code (memory leaks or unwanted behavior), it pushes you to the concept of “ownership”.

In a nutshell: variables live until the end of the current scope (or curling bracket), as these are “owned” by it. These can be moved into another function, invalidating it further, or “borrowed” by it.

let borrowable = "borrow it";
let consumable = "consume me";
borrow_variable(&borrowable);println!("Borrowed: {}", borrowable); <-- This works.consume_variable(consumable); <-- consumable went to this functionprintln!("Consumed: {}", consumable); // <--- This will error

There is a whole section about ownership, but the gist is you won’t ever need to “free” a variable from memory (deallocating) because Rust will do it for you at the end of the function

Unless what you’re doing is ultimately complex, unsafe, or low level, you’ll never think about allocating and deallocating, which makes your code cleaner.

7. The Stack, and his sister, the Heap

Memory in Rust is “semi-visible”, meaning, you can handle it in two ways: in the Stack, or the Heap.

The Stack is part of the memory that is used for everything known at compile time, like strings, integers, floats and even arrays — we’ll get into that shortly. For example, if I hard code “Mom’s spaghetti” in the application, it will be pushed to the Stack when the program execution reaches that part.

let in_the_stack = "Mom's spaghetti".

The Stack is fast because it just works like a column of plates: you can only add or remove from the top. The application knows how much space to allocate for that string alone.

Otherwise, you will mostly use the Heap, where data can grow or shrink depending on what’s happening, but is a couple of cycles slower. For example, a variable that holds what the user inputs will be saved in the Heap, since we won’t know how big it is until the program executes .

let in_the_heap = String::from(user_input);

While PHP mostly uses the Heap and allocates and deallocates memory constantly, this comes with performance penalties since the program must “jump” between the two, and also run the Garbage Collector.

There is an article that briefly describes how the Stack and the Heap compare in Rust, but in the meantime, you will want to always use the Stack if you want performance and you know at compile time what you are going to put into it.

8. Concurrency is first class citizen

PHP is a single-threaded language. There is no support for concurrent operations, meaning, two or more things happening at the same time.

Everything that can block the Request-Response operation, will block it: database connections, server-side requests, reading a file, etc.

public function thisWillBlockTheThread() {
$result = HTTP::get("http://www.google.com")->getBody();
echo $result;
}

Fortunately, in Rust we can create concurrent threads to handle data, instead of using a single thread that can be blocked by whatever.

use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("Hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("Hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}

handle.join().unwrap();
}

As of today, most CPUs come with 4 cores and 8 threads, so if you need performance, you can split your logic in the number of available threads for the process or system overall. This can be very good for Web Servers, Transcoders, or anything that doesn’t have race conditions and can benefit from concurrency.

9. Do something while doing something

Also not known in PHP are the asynchonous operations, something you may have heard as async-await.

The concept is relatively simple: you use async to declare an asynchronous function. These returns a “Future”. You can block the thread until it is resolved, and even wait for other async functions inside another async function with await:

async fn learn_song() -> Song { /* ... */ }
async fn sing_song(song: Song) { /* ... */ }
async fn dance() { /* ... */ }
async fn learn_and_sing() {
// Wait until the song is learned before singing it.
let song = learn_song().await;
// Then wait until the singing finishes before returning.
sing_song(song).await;
}
async fn async_main() {
let f1 = learn_and_sing();
let f2 = dance();
// Wait for both functions, in a concurrent manner, to complete.
futures::join!(f1, f2);
}
fn main() {
// Wait until the main async function ends its execution.
block_on(async_main());
}

The async-await system in Rust was introduced as stable feature a year ago. It’s not something you will find anywhere, but it’s something to note that most important Rust crates changed their code quickly to support the native feature.

Coming from the PHP world, these concepts can be difficult to understand at first, but believe me, once you grasp them you will be leaping in performance like a frog. There is a separate section of the documentation that goes into async-await.

10. These are not the arrays you’re looking for

Arrays in PHP are very, very lenient and dynamic compared to Rust. For example, this is totally normal in a PHP application:

$array = [
"one", <--------------- A string
2, <------------------- An integer
(object) ["three"], <-- An object casted from an array
]
$array[] = "four"; <----- You added something

If you think that will fly on Rust, no sir. Rust makes a revelation for you: in PHP you always declared something like HashSets with Enums, not “arrays”, and these PHP arrays always have some additional memory to allow grow — that’s why sometimes it’s better to use Objects instead of arrays in PHP if you’re very memory constrained.

Arrays in Rust represent a list of elements of the same type (like integers) and a known size at compile time (stated in your code). So forget about doing the same code in PHP and in Rust:

let object = Diamond {
pristine: true,
size: "large",
}
let proper_array: [usize; 3] = [10, 20, 30]; <-- This compileslet array = ["one", 2, object]; <--------------- ...but this won't

This is because the compiler tells the machine to reserve a size to hold the array in its entirety in the Stack. In the case above, we need 8 bytes for 3 integers, so we have 24 bytes in total. Otherwise, it will have to reserve a free space large enough to allow it for growing or shrinking, which is the Heap.

On the other hand, a HashSets (or HashMaps) with Enums for each type you plan to hold is what you would know as an “array” in PHP, like stated in the article linked before:

use std::collections::HashMap;#[derive(Debug)]
enum Value {
Str(&'static str),
Int(i32),
}
fn main() {
let mut map = HashMap::new();
map.insert("a", Value::Str("1"));
map.insert("b", Value::Int(2));
for (key, value) in &map {
println!("{}: {:?}", key, value);
}
}

Just remember that using arrays (or its “slices”) is good when you know their size at compile-time, Vectors if you don’t know, HashMap or HashSet for when you need key-value stores, and HashSets with Enums for values of different types and you still expect to grow or shrink, like when you receive a JSON string from another place.

But, what if you need a fixed “list” of varied types, like an integer, a boolean and another type? That opens up the next point: structs.

11. Objects are separated into data and behavior

Rust is not an OOP language. Not in the slightest. Is mostly a function-driven language, and you will have a hard time trying to replicate that pattern you had in PHP.

Instead, Rust offers you Structs and Implementations. The “Structs” are structures, and exists to handle different properties with different types in one bag. That way, the computer knows where and how much space to reserve in memory when instantiating that struct and its members.

struct Bucket {
name: String,
size: usize,
is_big: bool
}
let bucket = Bucket {
name: String::from("My yellow bucket"), // This went to the Heap
size: 15, // This to the Stack
is_big: false, // This to the Stack
}

These are just structures, and with that there is no behavior to it. If you want behavior, you have to implement it using the same Struct name.

impl Bucket {
pub fn new(name: String, size: usize, is_big: bool) -> Self {
Bucket {name, size, is_big}
}
pub fn what_is_my_name(self) -> String {
self.name
}
}

So there you have separation of data and behavior. Sometimes you will need only Structs, others you will need a Struct with some logic to call.

12. Tuples, Newtypes and Enums exists, by the way

Three things that now you will have to handle are Tuples, Newtypes and Enums. These are not “required” for any application, but knowing these exists can make your code less and in a more understandable way.

You can think about Tuples as structs without members names. These are useful to quick wrap multiple and different values in one bag, and when you are sure you won’t need to add behavior to them later.

let my_tuple(i32, &str) = (10, "Apple")fn return_a_tuple() -> (i32, String) {
(8, String::from("Pineapple"))
}

Tuple Structs are in between of Tuples and Structs. These are declared like Structs, so they have a name, but its members are not named. Instead, you access its member with their index, starting from zero.

struct TupleStruct(i32, &str);fn make_them() {
let my_tuplestruct = TupleStruct(10, "Apple");
println!("I have {} of {}", my_tuplestruct.0, my_tuplestruct.1);
}

Newtypes are Tuples Structs… with one value. You’re basically making a new “Type” of something, which can make your code more readable.

struct Color(&str);fn make_them() {
let color = Color("red");
what_color_is(color);
}
fn what_color_is(color: Color) {
println!("My apple color is {}", color.0);
}

Finally, something that doesn’t exist in PHP, but on the majority of other languages do, are Enums. These are basically a type that can change its state to a given option.

enum FruitFamily {
Apple,
Banana,
Pear,
Watermelon,
}
struct Fruit {
family: FruitFamily,
color: Color
}
fn what_fruit_is(fruit: Fruit) {
println!(
"This is a {} and my color is {}", fruit.family, fruit.color
);
}

Yeah, more tools for your application to work with. As always, you’re free to use whatever you feel is more appropriate. In the end, the more readable the code is, the less strain is to maintain it.

13. Self, mutable self, and referenced mutable self

When implementing behavior into a struct, sometimes you will want to reference the object itself. In PHP you use $this, so for example, we can print the name of the car easily from within a method.

class Dummy {
protected $name;
public function carName() {
return $this->name;
}
}

In Rust, you will talk about self, &self, and &mut self:

  • The self will “move” the object to the function, invalidating it after is used.
  • The &self will borrow it, but won’t be able to change it in any way.
  • The &mut self will borrow it and be able to change it.

Depending on what your function does, you will have one of the three, and because of the non-OOP nature of Rust, you will mostly use the last option to keep the object valid from the scope it was called.

Also, the self is magic. If you use it in your function as first parameter, you won’t need to issue it from the outside. In other words, you get the object magically for free.

struct Car {
vendor: &'static str
}
impl Car {
pub fn vendor(&self) -> &str {
self.vendor
}
}
fn main() {
let car = Car { vendor: "Toyota" };
println!("The car name is: {}", car.car_name());
}

If you see the car.car_name() call, you will see we didn’t have put the object itself inside as parameter. And that cues in the next point.

14. No optional. Fox Only. Final Destination.

In PHP, functions and methods can have “optional” parameters, meaning, it’s okay if you don’t put value as the method will gladly push a default for you.

function thisIsCool($name, $age, $is_cool = true) {
// ...
}
thisIsCool("John", 37); <---- This will work.

In Rust there is no such concept: if it doesn’t comply, it doesn’t work:

fn this_is_cool(name: &str, age: u8, is_cool: bool) {
// ...
}
this_is_cool("John", 37); <--- This will explode in your face!

As you can see, if you don’t issue ALL the parameters, your code won’t compile. Functions declared in Rust don’t have an “optional” parameter you can set. You will have to always specify all its parameters, even if you don’t like it, including how they receive the variable and what they output.

But what if you really don’t like it?

15. Option is your friend, Match is your bestie

In PHP, is totally fine to make something null, meaning, it doesn’t exists.

$thisIsInvalid = null;

In Rust there is no “null” or “void” concept mainly because a pointer can’t point to emptiness, and expecting emptiness without warning can break your application.

Instead, the solution is to use Option, a very simple Enum that accepts “Some” and “None”, and the match expression to execute on both possibilities.

fn there_can_only_be_one() -> Option<&'static str> {
let n1: u8 = rand::thread_rng().gen_range(1, 20);
if n1 > 10 {
Some("It's over 100")
} else {
None
}
}
match there_can_only_be_one() {
Some(string) => println!("The number is... {}", string),
None => println!("There is no valid number")
}

For example, you can use this to create an Article struct that may have or not an excerpt:

pub struct Article {
excerpt: String,
body: String,
excerpt: Optional<String>
}

16. Traits are now Interfaces. Yes.

Traits in PHP are basically shareable copy-pasted code over a class, while an Interface is something like a “Contract” for a class. Is not surprising that some Interfaces also come with traits that fully implement them.

interface Sounds {
public function sound();
}
trait Sounds {
public function sound() {
echo $this->sound
}
}

On the other hand, Rust forces you to the Composition Over Inheritance pattern: Rust traits are both PHP Traits and PHP Interfaces, and can come with function bodies that you can override if you want. There is nothing to “extend” on or be based on, but only to implement.

use pacific::mail::MailerContract;
use pacific::mail::MailMessageContract as MessageContract;
pub struct Mailer;impl MailerContract for Mailer {
fn send() -> bool {
// Send the Mail
}
}
impl MessageContract for Mailer {
fn message(body: String) -> Self {
// Create a new message
}
}

You can forget about abstract classes, dangling traits and figuring out interfaces.

Using this approach, it becomes very easy to maintain a struct that does a job, instead of “extending” another object and pay for the overhead that you may never use in the first place. You can have a Struct that does many jobs because it implements many traits.

But what if we want to implement something that returns a type that depends on the object itself. Wouldn’t we have to create multiple implementations for each type? In the next parts we talk about generics.

17. Welcome to Generics world

A way to make a function, struct or trait, be compatible with anything, is using “generic types”. PHP doesn’t support generics because of its dynamic nature, but here in Rust, where everything must be typed, generics are almost like a necessity.

Imagine that you have a Car, a Bicycle and a Plane, each with different wheel sizes. You can implement a trait that offers a way to replace an old wheel with a new one, and return the old. The problem? These wheels are different, so a function can’t just return “Wheel”, it has to return the type of the wheel for the vehicle, and all of them are different.

The solution? Instead of making a function for every vehicle, we can just make the ReplacesWheels generic over the type of the wheel.

trait ReplacesWheels<WheelType> {
fn replace_wheel(&mut self, new_wheel: WheelType) -> WheelType;
}
struct BigWheel {
name: String,
}
struct Plane {
wheel: BigWheel
}
impl ReplacesWheels<BigWheel> for Plane {
fn replace_wheel(&mut self, new_wheel: BigWheel) -> BigWheel {
// This replaces a property while returning the original
std::mem::replace(&mut self.wheel, new_wheel)
}
}

Generics are mostly the only way to make a logic work over anything that has different types, and you will see many functions, traits and structs with generics.

18. Namespaces are modules

This is one can a bit of a misunderstanding. Namespaces in PHP are declared in the file, and mostly follows the PSR-4 standard, meaning, it should mirror the directory structure where there are in.

// MyVendor/MyPackage/MyClass.php
<?php
namespace MyVendor\MyPackage\MyClass;...

The file would live in MyVendor/MyPackage/MyClass.php, but you can also change the namespace to something else. You could even put the “wrong” namespace.

In Rust, there is no concept of “namespacing” as you know in PHP. Everything here are “modules”, and under these modules live your pieces of logic.

pub mod foo {
pub mod bar {
pub fn do_something() -> bool {
// ...
}
}
fn quz {
// ...
}
}
fn main() {
foo::bar::do_something();
}

Now, you may have a lot of files in your project, not only the main file. In that case, the name of the file becomes the name of the module, so we can do something like this:

// src/foo.rs
pub mod bar {
pub fn do_something() -> bool {
// ...
}
}

Now that you have that, you can still call if from your main.rs:

// src/main.rsmod foo; <-- Use the module foo.rsfn main() {
foo::bar::do_something();
}

It takes a while to handle, specially when you start to use external Crates, which are some sort of Composer packages but for Rust. Also, modules are not declared by their directory; these must be referenced by a parent module in a parent directory, otherwise the compiler won’t know where it is.

In a nutshell, there is no need to write giant wall of texts on files if you can modularize it, and mostly you will find that you should.

As you saw, there is the pub keyword on some lines. That is visibility.

19. Public or Private, there is no in between

In PHP, the visibility of items, like class methods or variables, is a three-level store: public, protected or the-root-of-all-evil private, the latter lately in abandonment due to the extensible nature of Composer packages.

In Rust, on the other hand, you have only two options: “private” by default, or public. The semantics are mostly the same, and there are some advanced usages, but you get the idea.

If you wonder why “protected” doesn’t exists, is because Composition over inheritance. There is nothing to extend in Rust, so what is implemented under a trait can always be overridden.

pub mod public_api {
pub struct PublicBox {};
struct PrivateBox {};
pub fn do_something() -> bool {
// ...
}
fn internal_function() -> bool {
// ...
}
}
fn main() -> {
public_api::do_something();
}

Everything you create in Rust will be hidden from the public, which is good to avoid another developer trying to access something that shouldn’t and whine about it when you change it or having to upgrade the major version number because it breaks everything that was non-intended to do so. /rant

20. Composer? Cargo, did you mean.

Composer is an external tool to manage your PHP packages, and as of today is the best non-official package manager for PHP. Yes, PHP doesn’t have an official package manager, so if it explodes, PHP folks will just me’h it and the community will have to build something from the ground up, again.

Rust comes with Cargo. It handles packages called crates, and these are registered in crates.io. Everything is official, out of the box.

[package]
name = "my_proyect"
version = "0.1.0"
authors = ["John Doe <johndoe@mail.com>"]
edition = "2018"
[dependencies]
time = "0.1.12"
regex = "0.1.41"

It works almost the same, but one of the neat differences is that crates don’t live in the proyect under a vendor directory like when you use Composer, but on the user profile directory. You set your dependencies in the Cargo.toml and that’s mostly it. If you want to use a crate in your code, you just import it with the crate statement.

use crate::time;

For comparison, as time of writing:

So don’t brag about having “something for everybody” unless you are a Node.js developer and you know the good, bad and ugly of using NPM blatantly.

21. There are a lot of “non-stable” crates out there

If we’re doing something by SEMVER, you’re gonna find a lot, but a whole freaking lot, of crates that are not shy to say the haven’t hit an stable version. I’m not the first asking about that, and not even Cargo has reached v1.0.

While in PHP is normal to have packages that start a v1.0, crates in Rust tend to go for a non-stable version for one, two or the three main reasons:

  1. No commitment to stable API.
  2. Some features on Rust needs to exists to become stable.
  3. Currently in ongoing development.

This doesn’t mean whatever you code will be unstable too. On you dependencies list you always are encouraged to put the exact version for the crates you need, like we did in PHP, and only use version bounds for stable versions of packages.

If you want some good news, Tokio, the most used framework to create asynchronous and concurrent applications, it’s gonna hit v1.0 soon in a responsible way, by offering support and maintenance.

22. Testing is just a little… better?

While in PHP you have libraries like PHP Unit, Faker and Mockery that can help you with Integration tests in someway, in Rust you are encouraged to do only Unit tests.

For those who don’t know, Unit Test only test a particular functionality of a the application, while Integration Tests do test the whole result. It’s the difference between testing if you can aim and pull the trigger separately, and actually shoot the objective.

Rust even tells you that Integration Tests belongs outside of Rust, which is kind of true considering in Rust there is small space for unwanted behaviour. For that reason, mocking and faking exists but non-officially.

pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}
fn internal_adder(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
}

Also, testing is very straightforward, and you can do tests alongside your source code, or separately in another file like another module like src/tests/.

There is a nice chapter about testing in Rust and an answer in Stack Overflow about that, but in the meantime, consider that, unless you plan to do something very complex, integration tests exists but only some smalls steps over PHP.

23. No reflection, just macros

In PHP, since its an interpreted language, there is no problem to get the name of a function, or the current namespace, or even the class name. You mostly use the Reflection class for this task, or the functions like get_class().

class Something
{
public function myName() {
echo get_class($this);
}
}

In Rust there is no such concept, since functions and variables become bitcode, so it’s impossible to know that when your program actually runs.

As an “alternative”, macros exists. It’s some sort of “meta-language” which can generate more code, and can accept arguments like functions or variables to expand on these.

Rust come with some macros, like the vec!, which creates a Vector from an array like you would Vector::new(). Behind the scenes, it takes your code and makes it more “readable”.

fn show_me_the_number() {
// Using this macro:
let mut vector_with_macro = vec![10, 20, 30];
// Is the same as doing this:
let mut vector_with_struct = Vec::from([10, 20, 30]);
}

In the case above, some macros are more convenient than others, since these can evaluate the arguments passed on to it and create the code that better suits for the case. In the example above, if there are no arguments, it will automatically call new() instead of from().

But that’s just the tip of the iceberg, since there are also derives, function-like and attribute-like macros. You can also make your own macros. If you are curious, you could check the macros chapter.

24. There is Frameworks for the web

One thing is coding the language, and another is reinventing the wheel. Luckily, there are some frameworks for Rust so you don’t have to. Now, there is no “do-all” framework like Laravel or Symfony, considering the focused web scope of PHP itself, but rather frameworks that enable your project to do something.

You won’t be able to replace your whole PHP stack entirely, but only a core logic— to me, is clear that Rust will drive on microservices rather than full-stack web frameworks like those that exists in PHP. You will probably look into Actix, Rocket and Gotham, among others.

There is some frontend frameworks that focus on WebAssembly but, trust me, there is not a single one that offers readable code that doesn’t look like someone decided to chew and puke HTML. Also, there are some templating engines you can try, but still nothing that could catch my attention personally like “Why I’m not using this!?”.

If you’re searching for something that allows you to add HTML templates, I would gladly use PHP instead. Otherwise, you may want to use JavaScript and frontend frameworks like Vue, React, Angular, Ember, you name it, and authentication layer, put it in a HTML Server and you’re done.

25. Databases in disarray

PHP offers a single standard to access to a database data, named PHP Database Objects, also known as PDO. Database connections in PHP are mature and nobody is arguing about that, and because of that there are a lot of ORM (Object-Relational Mapping) packages that make your life easier when dealing with database data:

$orm = Database::connection('sqlite')->table('articles')->all();

Rust, on the other hand, doesn’t have something like PDO. Instead, you have frameworks that have to do everything from the connection, to the schema declaration, to the retrieval of records, like Diesel, SQLx and RustORM. For example, Diesel also includes a query builder, but if you don’t have one you will have to plug an external one in your project.

So, it’s not that Rust is incompatible with databases. There are a lot of clients for SQL-based engines and even some NoSQL databases like MongoDB, Redis and Memcached. Is just that everyone is doing their own way to database, so there is no modularity.

In the meantime, you should keep a eye on RDBC, which proposes a common ground for all drivers to connect, execute and retrieve data from any database, much like PDO offers. PHP figured it out 15 years ago in the v5.1 version, and I humbly think RDBC this will become stable in the next year as long the guys behind Rust decide this is the way and not something like R2DBC, which offers a standard non-blocking database interaction as any database interaction 2020 onwards should be. I hope is the latter, though.

As you can see, there is a lot of room for discussion beyond the basic approach.

Bonus: The compilation time is slow

Let’s get this out of the way: deploying PHP can be faster or slower depending on two things:

  • How many dependencies you may download with Composer.
  • What extensions you may need to compile for your PHP runtime.

And that’s it. In Rust, however, no matter what you do, it takes its sweet time to make a binary out of your code. This is mainly because three reasons, as stated in the Rust FAQ:

  • Rust must check every part of the code for safeness.
  • Rust generates some bad old code that the LLVM must fix at compile time.
  • LLVM, the framework behind compiling Rust, it’s not made for fast compiling.

Summing up the three, and you have slow compilation time compared to other languages. Luckly, you won’t suffer a lot from this when developing, but when deploying for production, you may have to wait a little until your binary is ready to copy it around — don’t Rambo it and compile the same thing, over and over again, in each machine.

Well, at least I don’t have to deal with file permissions on multiple directories, and keep in sync the user executing NGINX/Apache with PHP.

It’s clear that Rust is a swiss knife for all sorts, considering it is safer to pick up compared to C++ or other similar system languages.

There is a index of “Are we X yet?” that summarizes the state of the language and ecosystem to support a niche. For example, GUI capabilities in Rust are in a very green state, and creating a game engine depends on you glueing everything together.

Being that said, it’s not weird to think to make a web application that receives a files and transcodes it in real-time. That’s the good part of Rust: you can put whatever you want since the language, while being statically typed, can do everything. You could even make a software for an embedded device and serve an API so other appliances can connect to it.

So there, Rust is awesome, not as mature as PHP considering it has more than 35 years, but considering is a very safe language and close-to-metal performance, it will probably become very important in the next years. I mean, if I could understand the main differences from a very lenient and lazy perspective, you can do it too.

--

--

Italo Baeza Cabrera
The Startup

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com