Why Rust?
Lecture 2: Friday, January 23, 2026
Code examples
A common question we get from students is why did we chose to use Rust for this course instead of a different language? Why not continue with Python since it was used in DS 110?
It is important for us to explain our rational in some detail, and demonstrate to you the motivation behind these decisions. Throughout the course, you will struggle with some Rust-specific concepts and idiosyncrasies. We want you to understand that there is a reason you have to put up with (and overcome) all the hurdles of learning a new languages and in dealing with the Rust syntax, compiler, and borrow checker.
Why learn a new programming language? Why not continue to use Python?
Learning a second programming language builds CS fundamentals and teaches you to acquire new languages throughout your career.
Importantly, it also helps you distinguish what is an inherent property of how computers or algorithms work, and what is simply an incidental design decision, implementation choice, or a convention from a particular programming language ( and why that language made these choices, which often has deep and interesting justifications).
Example 1: What do we mean by an incidental choice?
Consider the following Python code:
print('hello world!')
We are all familiar with what this code does. However, nothing in it is particularly insightful about the essence of computers
or programming or even necessary given how they work. The fact that printing uses the word print is incidental. Other languages use
a different word for it, e.g., println in Rust, cout in C++, or console.log in Javascript.
In Python 3, printing is a function call, as evident by the paranthesis following print above. But it does not have to be this way:
in Rust, it is a macro (as we will see later), and even in Python 2, you could print using print "hello world".
Finally, Python does not really distinguish between " and ', but other languages, like Rust, do!
Example 2: Diving deeper into good and bad design choices
Consider the following Python code:
x = 2
y = '3'
print(x + y)
What is the output of running this code?
The above code produces a runtime error when run:
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Is this the result of a fundemental aspect of how computers work? Not really. In fact, other languages similar code will have different behaviors.
In Javascript and Java, 2 + "3" results in "23"! These languages implicitly upgrade 2 to a string. Interestingly, in Javascript, 2 * "3" give 6, but not in Java!
If you were responsible for designing your own language. What alternative behaviors would you consider for
2 + '3'?
Some of your answers:
- "23"
- 5
- an error only if the string is truly a string, e.g.,
2 + 'hello', and 5 otherwise.
Some other answers we think would not be good:
- 0 or the empty string
- 5 for
2 + '3'but2hellofor2 + 'hello' - The number 23 (as a number, not a string).
It seems like there is a range of answers that are acceptable/somewhat reasonable, and a range of clearly unreasonable answers. What are the criteria that distinguish the two?
- Whether the choice reflects our natural/intuitive expectations as humans/programmers
- Whether the choice is confusing or has many corner cases
- Whether the choice results in silent failures if the program is executed that are hard to debug
Example 3: What are the tradeoffs at play when looking at competing choices
Consider the following Python code:
if 10 > 0:
x = 5
print(f"hello world {x}")
The below Rust code is equivalent:
fn main() { if 10 > 0 { let x = 5; println!("hello world {x}"); } }
Running both pieces of code (e.g., using the Rust playground) reveals
that they both produce identical output: hello world 5.
What are the most visible differences between the two code snippets
- The Rust code is contained within a function whose name is
main. - Python uses
:and indentation, Rust uses{and}. - Python delimits statement with new lines, Rust uses
;. - Variable declaration looks different, python does not distinguish from initial variable declartion and changing it in the future, Rust does by requiring initial declartion use
let.
We will understand what (1) and (4) mean later in the course. Let's focus on (2) and (3).
The following code is valid in Rust, however the same style in Python would not work: (a) the code is not indented properly, and (b) multiple statements share the same line.
fn main() { if 10 > 0 { let x = 5; println!("hello world {x}"); } }
Which do you prefer?
- Some of you prefer Python's style. Reasons include:
- being easier to read
- looking nicer and shorter (no
mainfunction) - you are already more familiar with Python: that code feels more natural
- Others prefer Rust:
- no issues with indentation (e.g., mixing spaces and tabs): these are a major headache when writing Python code
So, both approaches have nice things (and not so nice things). What dimensions are at odds here?
How nice the code looks and how easy it is to read vs how easy it is to write! E.g., indented code looks nicer
and shorter than code with { and }, but dealing with spaces and tabs is a headache while writing the code.
We call the first readability and the second writability. Sometimes these are subjective, sometimes they are less so.
We will see many more examples later.
Okay, you convinced us that we should learn a second language. Why Rust specifically?
Rust is a compiled language! It is very fast! It is also very different from Python!
What about other compiled and fast languages, like C++?
Compared to C++, Rust offers two important advantages:
Memory safety: Rust lets you see how data structures work in memory and manage your own memory, while reducing the risk of making various memory-related mistakes (e.g., a lot of the C/C++ headaches)
Strong type system: Rust has a strong type system that helps programmers writing Rust code ensure their programs are correct, memory safe, and type safe. We will understand what that means in the future.
In other words, the Rust compiler gives you errors and hints about your code to help you write code correctly, rather than allow you to write code any way you want, and then have to deal with fixing it when you encounter various errors during runtime.
Furthermore, Rust is experiencing growing adoption in industry and academia a like. This spans many fields, from low-level systems programming (e.g. the Linux kernel), software engineering, to data science and scientific computing!
We will investigate these notions in more depth later.
Rust, The Rust Compiler, and Speed
A big reason for why we want to teach you a compiled language, and specifically Rust, is that it is fast. But how fast is Rust (or compiled languages in generaly) you may ask.
Consider our very first, proper code example.
In this example, you can see two equivalent pieces of code in Rust and Python. The code first creates a list with many numbers (10 million numbers!), then sums all these numbers using a simple loop.
In the code, we measure the elapsed time between the start and end of that loop. In other words, the time that it takes to sum up the 10 million numbers.
First, we run the python code using cd src && python3 example.py. On my machine, the sum takes ~310ms. This is impressive! Imagine how long it would take
you, a human, to add up 10 million numbers.
Next, we run the rust code using cargo run. On my machine, the result takes 30ms. That is a 10x improvement! In other words,
if you were a company in the business of adding numbers (which is more or less what all the big AI companies do), you just cut down
your compute costs by a factor of 10! Big savings!
But wait. It gets better. We can ask Rust (specifically, the Rust compiler) to automatically optimize our code as much as it can using
cargo run --release. Now, the result takes ~1.5ms an impressive 200x speedup, or alternatively, a 200x reduction in compute costs.
BIG SAVINGS.
REALLY REALLY BIG SAVINGS.

What is a compiler? Why is it fast?
The content of the python file example.py is human-readable (some may disagree). It contains things like comments, variables with intelligible names, among other things.
Importantly, it is not written in a computer's native tongue: the ol' zeros and ones.
Notice how we ran the python code. We first type in the python3 command, then give it the name of the file we want to run. The python3 command is a translator: it is
responsible for reading out the file one line at a time, and translating it to the computer as it goes through it, telling the computer what to do.
This is a lot like when you watch a live TV speech in a foreign language: the TV station super imposes the voice of a live interpreter, that translates every sentence from the speaker's
language to English, allowing us to understand what the speaker says, but, often at a noticable delay.
The python3 command does exactly that (which is why it is infact called the Python interpreter), except the delay it introduces is even more significant (in relative terms), thus causing slower execution of code.
Rust instead does not have an interpreter, it is a compiled language. We can see how that operates using the following commands in our example code above:
# This compiles (or builds) but does not run the code
cargo build
# This takes us to the folder where the compiler produces the compiled code
cd target/debug/
# The compiled code is in a file called example
# This runs it
./example
You can see that our Rust code, once compiled, can be run directly, without any other commands: there are no interpreters!
Furthermore, we can print the contents of the compiled file. Note that it is not human-readable at all! This is all in zeros and ones, in the computer's native tongue.
# This takes us to the folder where the compiler produces the compiled code
cd target/debug/
# print the content of the compiled file
cat ./example
Finally, cargo run is simply a helpful shortcut that first compiles the code, then runs the compiled code.
Rust lets us get more visibility into how the computer works
Look at our second code example:
rows.rs and columns.rs are almost identical. Both create a large matrix of dimensions 10,000x10,000 (100 million elements) and sum all its elements
while timing the duration required for the sum.
The one difference is that rows.rs goes through the matrix one row at a time, starting from the first row. While the columns.rs goes one column at a time,
starting from the left most column.
Both add the same number of elements (100 million), so it stands to reason that they will take a similar amount of time. Let us see if that is the case:
cargo run --bin rows # runs rows.rs
cargo run --bin columns # runs columns.rs
On my machine, there is a noticeable difference: columns.rs is roughly 3x slower than rows.rs! Why?
It turns out this difference is entirely related to how the computer works, and not the algorithmic nature of either code (which is similar). Specifically, it has to do with how the CPU in a computer fetches the numbers from its memory (i.e., RAM), and how the numbers inside the matrix are placed in that memory. We will look at the details of computer memory and its structure later.
Key takeaway: the important lesson is that we need to understand how the computer works to inform us in how to write the most efficient formulation of our desired program.