Rust Basics: A Beginner's Dive into Ownership, Syntax, and More
I’m a student with some experience in C/C++ and JavaScript, and recently I decided to take on the challenge of learning Rust. Why Rust, you ask? Well, it’s known for its performance, memory safety, and growing popularity in system programming, and I couldn’t resist exploring it! In my first week, I explored Rust's basic syntax, ran my first "Hello, World!" program, and started learning about key concepts like variables, ownership, and borrowing. Rust is different from C++ in its approach to memory safety, but it still feels familiar due to its systems-level focus. I’ll also relate these Rust concepts to my knowledge of C/C++ and JavaScript where I can.
Getting Started: The First “Hello, World!”
The first thing I did was set up Rust on my machine and run the classic "Hello, World!" program. To do this, Rust uses Cargo, its package manager and build system, which reminded me a bit of npm in JavaScript. Here's how I got my first program up and running:
cargo new hello_rust
cd hello_rust
cargo run
This automatically creates a new project with a src/
main.rs
file, which is where I wrote my "Hello, World!" program:
The process of using cargo run
to build and run the program was smooth.
fn main() {
println!("Hello, world!");
}
Understanding Variables, Mutability, and Shadowing
Rust’s variable handling is stricter than what I'm used to in JavaScript. In Rust, variables are immutable by default, meaning you can’t change their value after they’re set unless you explicitly make them mutable using mut
.
In JavaScript, variables are often declared with let
(which is mutable), and const
for immutability. In Rust:
let x = 5;
x = 6; // This would cause an error because x is immutable.
If you want to make a variable mutable, you have to do this:
let mut x = 5;
x = 6; // Now this works because x is mutable.
There’s also a concept called shadowing, where you can declare a new variable with the same name as an existing one. In C++ and JavaScript, this would typically cause an error or conflict, but Rust allows it:
let x = 5;
let x = x + 1; // This shadows the previous x with a new value
println!("The value of x is: {}", x);
This concept is similar to re-declaring variables with let
in JavaScript but much safer because Rust keeps track of the scopes and ownership.
Exploring Data Types
Rust is a statically-typed language, which means the compiler needs to know the types of all variables at compile time. This felt very similar to my experience with C/C++. Here are some basic data types in Rust:
let x: i32 = 5; // 32-bit integer
let y: f64 = 2.0; // 64-bit floating point
let is_active: bool = true; // Boolean
let character: char = 'A'; // Character
In Rust, you have primitive types like integers (i32
, u32
), floating points (f64
), and booleans (bool
). The need to explicitly declare types sometimes feels like writing C++ code, but Rust has a cleaner syntax and type inference, making the experience less verbose. One interesting thing is that Rust’s char
is not just 1 byte like in C/C++. It’s 4 bytes, allowing it to store more than just ASCII characters, which I found pretty cool.
Functions and Control Flow
Functions in Rust are similar to C++, but a bit cleaner in syntax. Here’s how a basic function looks:
fn add(x: i32, y: i32) -> i32 {
x + y
}
The -> i32
specifies the return type, much like C/C++. Rust functions return the last expression implicitly without needing the return
keyword.
Control flow (if
, else
, while
, for
) is similar across JavaScript, C++, and Rust, but one thing I found cool is that if statements in Rust are expressions. They return values!
let number = 6;
let result = if number > 5 { "greater" } else { "lesser" };
println!("The number is {}", result);
Paste the above code into your src/
main.rs
file, within the main function, and see the output.
Understanding Ownership, References, and Borrowing
This is where Rust really starts to stand out from C/C++. One of Rust's key features is its ownership system, which helps manage memory without needing a garbage collector like JavaScript or manual memory management like C/C++. Rust’s memory management system makes sure your program is safe by enforcing rules that prevent data races and dangling pointers, issues that are common in C++. By memory, I mean memory allocated on the heap, not the stack. When your code calls a function, the values passed into the function (including pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function ends, those values get popped off the stack. All data stored on the stack must have a known, fixed size. Data with an unknown size at compile time or a size that might change must be stored on the heap instead. If you are not familiar with how the stack and heap are used in low-level languages, you can find many articles on the internet about this topic.
Ownership Rules:
Each value in Rust has a single owner.
When the owner goes out of scope, the value is dropped.
You can’t have two mutable references to the same data at the same time.
In C++, you’re responsible for managing memory, which often leads to bugs like dangling pointers. Rust’s ownership system ensures that memory is automatically managed.
For example:
let s1 = String::from("hello");
let s2 = s1; // Ownership is moved to s2, s1 is no longer valid
In C++, you would have to manually free the memory, while JavaScript would automatically handle it. In Rust, it’s done safely but with compile-time checks that prevent memory errors.
Borrowing and References:
Rust allows you to borrow data through references, which allows multiple variables to read the same data without transferring ownership.
let s1 = String::from("hello");
let s2 = &s1; // Borrowing s1, s1 is still valid
println!("{}", s1);
println!("{}", s2);
This concept is similar to pointers in C++, but without the fear of dangling pointers because Rust enforces strict borrowing rules.
Rust also prevents data races by ensuring that if you have a mutable reference to data, you can't also have an immutable reference at the same time. Data races cause undefined behavior and can be difficult to diagnose and fix when you’re trying to track them down at runtime; Rust prevents this problem by refusing to compile code with data races!
let mut s = String::from("hello");
let r1 = &s; // Immutable reference
let r2 = &mut s; // Error: cannot borrow as mutable because it's already borrowed as immutable
In C++, you could have both, leading to unpredictable behavior, but Rust enforces these rules strictly at compile time.
Slices: Getting a View of Data
The last thing I learned this week was about slice types in Rust. Slices let you reference a portion of an array or string without taking ownership, which is somewhat similar to mixture of references in C++ and array slicing in JavaScript.
Try this example in your machine and see the output.
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{} {}", hello, world);
Conclusion
In my first week of learning Rust, I’ve discovered its unique approach to memory safety and efficiency, which sets it apart from languages like C++ and JavaScript. The strict rules around ownership, borrowing, and mutability ensure that common bugs are avoided, making Rust a powerful tool for system programming. The syntax is clean and modern, and the use of Cargo simplifies project management.
What’s Next?
I’ve barely scratched the surface of Rust, but I’m already impressed by its focus on safety and efficiency, particularly around memory management. Coming from C++, I appreciate the strict rules around ownership and borrowing, which eliminate common bugs. The syntax also feels cleaner and more modern than C++. Next week, I’ll be diving deeper into ownership, borrowing, and slices, writing a detailed post about how these work and why they make Rust such a powerful language.
If you’re also learning Rust, feel free to connect and share your journey too! Stay tuned for my next post as I go further into the world of Rust.
Subscribe to my newsletter
Read articles from Siddhesh Parate directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by