Rust for Python Programmers

Eric Greene November 13, 2023
Python to Rust Banner

Rust and Python

Rust is a system-level programming language that is rapidly growing in popularity and is loved by the programmers who use it. Take a quick look at the recent 2023 Stack Overflow Developer Survey. Their new chart on Desired/Admired programming languages shows how much programmers love Rust. While Python is more widely used, programmers do not love programming in Python as much as they love Rust.

2023 Stack Overflow Developer Survey Desired and Admired Programming Languages

Source: 2023 Stack Overflow Developer Survey

Recently, Microsoft announced that they are now including Rust code in the Windows Kernel, and Rust will be replacing the C and C++ programming languages when non-garbage collected languages are needed.

While Python is a very popular and easy-to-use programming language, it has limitations such as slow execution, inefficient use of memory, and limited parallelism because of the GIL. Unlike Python which is interpreted, Rust is compiled to native code with C-like execution speed, efficient memory use, memory safety, and true parallelism.

Python Code

Let's consider the following Python code. This Python code provides a loop for capturing user commands from a console application. Programming a looped command entry mechanism is easy in Python.

while True:
    command = input("> ")

    if command == "exit":
        break
    
    print(f"echo: {command}")

The while True: statement infinitely runs the loop until the break statement executes. The input function displays a prompt "> " and waits for the user to input some text. If the user types "exit", the loop will break, and the program will terminate. If the user types any other command, the command is displayed on the screen with the print(f"echo: {command}") statement, and the loop continues.

This is very simple, yet useful code snippet, to get started with any command-driven console application. How would the above Python code be written in Rust?

Rust Code

To explore how the above Python code is written in Rust first make sure you have Rust installed.

The Rust code below is one way of implementing the above Python code. As you can see, the code is much longer, and there are more details the code must consider. Lower-level languages require more specific details from the programmer. With Python, things just work but work slowly, with lower-level languages like Rust, the programmer takes on more responsiblity to make things work. What is the benefit? Better performance, more control, etc.

use std::io;
use std::io::Write; 

fn main() {

  loop {

    print!("> ");
    io::stdout().flush().unwrap();

    let mut command = String::new();

    io::stdin()
        .read_line(&mut command)
        .expect("Failed to read line");

    let command = command.trim();

    if command.eq("exit") {
      break;
    }

    println!("echo: {command}");

  }

}

Rust Code Walkthrough

Unlike Python, Rust does not use whitespace to organize code blocks. Instead, Rust uses curly brace syntax style (like C, C++, C#, Java, JavaScript, etc.). Nevertheless, the standard code indent size is 4 spaces, just like Python. Also, similar to Python, many control-flow statements, such as if statements, don't use parentheses around their expressions either.

Bring Types into Scope

In Python, the print and input are built-in functions that require no import statements. Rust has something similar to the built-ins that is called the prelude. The prelude automatically brings certain types from Rust's standard library into scope. The stdout and stdin types are not part of the prelude, so the use statements will bring these types into scope.

use std::io;
use std::io::Write; 

Main Function

Unlike Python programs that can contain code statements without a main function, Rust requires a main function to serve as the starting point of program execution.

fn main() {

  // code

}

Command Loop

An infinite loop is constructed in Python with the while True: statement. An infinite loop can only exit with a break statement. Rust has a special loop construct for infinite loops. Like Python, infinite loops are exited with a break statement.

loop {

  // ...omitted...

  if command.eq("exit") {
    break;
  }

  // ...omitted...

}

While there are other kinds of loops in Rust, having a dedicated syntax structure for infinite loops makes the code more readable. Python's while True: kind of feels like a hack.

Python's print function outputs text to the screen. In Rust, the statement print! is not a function but a macro. The macro is replaced by code that will print the argument "> " to the screen. The stdout flush function forces the output to the screen before waiting to read user input. The unwrap method is useful when you expect the operation to be successful and want to handle the result directly. However, it's important to note that using unwrap without appropriate error handling can lead to unexpected program termination if an error occurs.

print!("> ");
io::stdout().flush().unwrap();

In addition to the print! macro, a println! macro will print a new line at the end of the string.

println!("echo: {command}");

Declare a Variable

In Python, variables are not explicitly declared. Instead, variables are created when they are first assigned to. In Rust, the let statement declares a variable. By default, variables are immutable in Rust. To make a variable mutable, the mut keyword must be applied when declaring a variable.

let mut command = String::new();

Variables can be re-declared and shadow the previously declared variable.

let mut command = String::new();

// ...omitted...

let command = command.trim();

The type for the first declaration of command is String, and the type for the second declaration is &str. Commonly, shadowing is used to avoid declaring a second variable when the only purpose of the second variable is to be type conversion from the earlier declaration of the variable. Assigning values of different types is supported in Python because of dynamic typing. Nevertheless, dynamic typing is not the same as re-declaring a variable, even if it looks similar in this simple example.

Dynamic typing determines the type of a variable at runtime as the program executes. It is not possible to know for sure the type of a variable at development time. Rust's ability to re-declare a variable still determines the variables type at development time. There is no runtime uncertainty about the type of the variable at any point in the code.

Read Data from the Console

Python provides the built-in input function to collect user input. The input function prints a prompt to the screen and waits for the user to type in something and press Enter. In Rust, the stdin function only reads data from the user; it does not display a prompt. When the command variable is passed as an argument to the read_line function, the modifier &mut is applied to the argument. The command variable is passed by reference; its value can be mutated within the function. Rust is known for memory safety and has stringent rules about when memory can be changed and who owns memory. Because reading data can result in error, the expect function handles any errors.

io::stdin()
    .read_line(&mut command)
    .expect("Failed to read line");

If Statement

Similar to Python, Rust supports if statements. Observe that the conditional expression is not enclosed in parentheses. Instead of the comparison operator ==, the String's eq function performs the string comparison. If true, the break statement will cause the program to exit the loop mentioned earlier.

if command.eq("exit") {
  break;
}

Conclusion

Rust is a powerful system-level language that continues to grow in popularity and usage throughout the tech world. Python is a higher-level language used for many purposes, such as task automation, web applications, and machine learning. Sometimes, a program written in Python needs a more robust language that employs modern programming language features such as memory-safety and parallelism as well as significant execution speed boost. For such situations, Rust may be an excellent alternative to Python.

While not covered in this post, it is possible to write some parts of a Python program in Rust and call the Rust code from Python similar to how Python can call code programmed in C. Perhaps there will be a future post on this topic.