Rust is the language that we both need and deserve
Tags: rust, webdev, beginners
Published: 2024-02-17 23:03:45

I have worked as a Software Engineer for quite a while now. I have mostly used JAVA in my projects and am experienced in it (not an expert probably). But I feel constricted by it, especially when I think about nuanced code optimizations and GC. I love to use other languages like Python, JavaScript, and Groovy here and there if required because of their kinda non-" OOP first" approach. However now and then used to get a nagging feeling of insufficiency that we were not doing something right. Every language from C++ to Javascript has its glaring challenges, pitfalls, and limitations that we should accept and move along.
Javascript is so broken that everyday new frameworks are trying to fix the same problem

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/axnbpab1sh06fbsodyuy.jpg)

—the problem of non-determinism. Determinism is when assigning a value for a variable, setting a function return type, or returning an expression, the contract is set, and the contracts are infallible. Non-determinism creeps in when conditions like edge cases, and empty values are not realized and cause the contract to fail. A simple example of a contract is the type-safety guarantees that languages like JAVA provide but at a cost. If we wish to make some performance improvements because of limitations in memory and network speeds, we must do away with some type-checking stuff. Then there are other problems like "null" which we are trying to solve to this day. I never knew it was a problem, to be honest. And then I read about RUST lang a couple of years back and my whole perspective has changed forever.

**The Need We Didn't Know We Had:**

- Handling nulls and exceptions:

A lot has been said about null being a [billion-dollar mistake](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/). Rust handles the problem of null in a fundamentally different way compared to languages like Java/C++. Rust's approach revolves around its type of system, ownership model, and the concept of Option types, ensuring memory safety. Rust provides two constructs for that: the `Option<T>` enum and the `Result<T, E>` enum. Let's compare how Rust handles null with Java using an example:
In Java, null is a special value that represents the absence of a value for reference types. This can lead to null pointer exceptions if not handled properly.

```
public class Example {
    public static String getString() {
        // Simulate a condition where the value is not available
        return null;
    }
    public static void main(String[] args) {
        String result = getString();
        // Attempting to call a method on a null reference leads to NullPointerException
        System.out.println(result.length());
    }
}
```
In Rust, null references and null pointer dereferences are not possible due to its ownership system and the Option type. Instead of null, Rust uses the `Option<T>` enum to represent values that may or may not be present. This ensures that the possibility of null is handled explicitly at compile time.

```
fn get_string() -> Option<String> {
    // Simulate a condition where the value is not available
    None
}

fn main() {
    let result = get_string();
    // Attempting to call a method on an Option requires explicit handling
    match result {
        Some(string) => println!("{}", string.len()),
        None => println!("Value is not available"),
    }
}
```

`Result<T, E>` is another enum type provided by Rust's standard library. It represents the result of an operation that may succeed (Ok(T)) or fail (Err(E)). `Result<T, E>` is commonly used for error handling, where Ok(T) contains a successful result value and Err(E) contains an error value. Like `Option<T>`, `Result<T, E> `encourages explicit handling of success and error cases, promoting safer error-handling practices.


- Immutability by default:

In Rust, variables are bound using the `let` keyword by default, and these bindings are immutable by default. Once a value is bound to a variable using `let`, it cannot be reassigned to a different value unless the variable is explicitly marked as mutable. By enforcing immutability by default, Rust promotes safer and more predictable code, as the mutable state is minimized, reducing the risk of data races, mutable aliasing, and other common concurrency issues. But it goes a bit deeper still, as there are functions like `iter` which allows for iterating over the elements of the collection without modifying them. But if we would like to mutate the underlying data structure we can use `iter_mut` just as easily.

```
fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    // Using iter
    println!("Using iter:");
    for num in numbers.iter() {
        // num is an immutable reference to each element
        println! ("{}", num);
    }
    // Using iter_mut
    println!("Using iter_mut:");
    for num in numbers.iter_mut() {
        // num is a mutable reference to each element
        *num *= 2; // Double each element
        println!("{}", num);
    }
    // Print the modified vector
    println!("Modified vector: {:?}", numbers);
}
```

- unsafe rust:

While Rust's borrow checker is a powerful tool for preventing common memory-related bugs like data races and dangling pointers, it can sometimes be perceived as restrictive or cumbersome, especially when dealing with complex ownership patterns or mutable borrows. But that is what saves us from shooting ourselves in the foot. So, the question is how do we enforce these types and memory safety guarantees and allow developers to perform operations that cannot be expressed safely within Rust's type system? Rust tries to answer this with unsafe rust. As mentioned in the _[NoBoilerPlate ](https://www.youtube.com/@NoBoilerplate)_video 

> While other languages say you can't cross this line, rust says here is the line, cross it if you are sure what you are doing
. Unsafe Rust provides a way to bypass some of the safety guarantees enforced by the Rust compiler. This allows developers to interact with low-level systems. This also helps Rust to work "with" rather than "against" other languages and frameworks. One such scenario is when dealing with global or static variables in multi-threaded programs.
```
use std::thread;
static mut GLOBAL_COUNTER: u32 = 0;
fn main() {
    let mut handles = vec![];
    for _ in 0..10 {
        let handle = thread::spawn(|| {
            unsafe {
                GLOBAL_COUNTER += 1;
                println!("Incremented: {}", GLOBAL_COUNTER);
            }
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
}
```
**Why we deserve rust:**

- Type safety:

Java's type safety relies on static typing and type erasure, while Rust's type safety is augmented with ownership, borrowing, and lifetimes. Rust's ownership system provides stronger guarantees against common memory-related errors compared to Java's nullable reference types. Rust's explicit handling of nullable and error-prone values with `Option<T>` and `Result<T, E>` leads to safer code and fewer runtime errors compared to Java's nullable reference types and unchecked exceptions.

- Compiler-driven programming:

Most of the developers see the code while they write, not when it runs. So, if there are some issues with the code developers won't know them until the code is running. Rust leverages the compiler's features, such as static analysis, error messages, and type checking, to guide the development process and ensure correctness, safety, and code efficiency.
Let's consider an example from the book:
```
fn main() {
    let first = String::from("Ferris");
    let full = add_suffix(first);
    println!("{full}, originally {first}"); // first is now used here
}
fn add_suffix(mut name: String) -> String {
    name.push_str(" Jr.");
    name
}
```
We are adding a suffix to a variable first and displaying the original string and the new one. But we have a problem, as String "Ferris" is mutable adding a suffix can potentially modify the String or destroy it for all we know. Therefore, the potential reference first is undefined. On compiling we get the following error. 
```
error[E0502]: cannot borrow `name.1` as mutable because it is also borrowed as immutable
  --> test.rs:11:5
   |
10 |     let first = get_first(&name);
   |                           ----- immutable borrow occurs here
11 |     name.1.push_str(", Esq.");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
12 |     println!("{first} {}", name.1);
   |                ----- immutable borrow later used here
```

During my time working with Rust, I have understood my lack of understanding of possible scenarios to be covered and it has given me a lot of confidence while programming. It is true that once you write a program in Rust, it will run and if all tests are covered, they will not have any issues at runtime. Your work is done, and you are free.

- Documentation constructs:

Rust uses documentation comments, also known as doc comments, to document items such as functions, structs, Enums, modules, and traits. Documentation comments start with `///` for documenting the item they precede. The Rust compiler includes these comments in the generated documentation. Following is a small example.

```
/// Calculates the sum of two numbers.
///
/// # Arguments
///
/// * `a` - The first number.
/// * `b` - The second number.
///
/// # Examples
///
/// ``
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ``
fn add(a: i32, b: i32) -> i32 {
    a + b
}
```

Moreover, the assert you see is tested by the rustc before it is included in the documentation. This makes sure that if the function behavior is updated somewhere that doesn’t line, the documentation also needs to be updated accordingly. _Ingenious!_


- Community:

The learning curve for rust is very high, at least according to my experience with the language. But the Rust community has helped me a lot. The community is known for being welcoming, inclusive, and passionate about both the language itself and the broader goals of safety, performance, and concurrency.  Contributors come from diverse backgrounds, including individuals, corporations, and non-profit organizations. I followed the rust-book and rust-by example to start. I can say that they are the most comprehensive explanations I have ever seen. I also completed rustlings challenges after that and on top of that we have more books and documentation provided by the Rust standard library itself. This makes the initial journey of a learner manageable if not easy.

**Conclusion:**
This post is not meant to undermine the impact other languages have had on programming. Moreover, other languages like Haskell, Lisp, and Zig have similar features or features Rust has outright borrowed from. Languages like JAVA and C/C++ still have robust frameworks, tooling, and community support. Commercially these languages are still viable (I can't say the same about JAVAScript, I am sorry). Rust despite its advantages has a lot of challenges. For example, while working with Axum (a web framework like Spring Boot or Flask), I had to add a special annotation and a special macro package to get some details on the issue. Also, for some time I will have to contend with a slower development process till I am capable of understanding all the Rust features (read skill issue). However, I have learned a lot about programming while learning Rust and I am seeing the compiler more as a friendly instructor rather than a field marshal. So overall I am happy to learn and hope Rust receives more mainstream attention.