It's a common story: the basics always seem easy to learn until we try to apply them in real-world situations, where numerous issues arise. Why is it that documentation provides only a few simple commands, but when we read a real project, it seems to be full of Martian syntax? @@.
In reality, documentation should guide us from the fundamentals to advanced topics, rather than introducing complex syntax right away. If we have a solid grasp of fundamental knowledge, we can still write our first program. As we become more accustomed or work on various real-world projects, naturally, getting acquainted with new things becomes faster. Therefore, fundamental knowledge lays a strong foundation for us before we venture into more significant tasks.
Last week, I went through Rust's documentation from start to finish, focusing on syntax and the initial fundamental concepts.
Rust supports multiple platforms. You can install Rust with a single command if you're using macOS or Linux.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Or, you can explore other installation methods at Other Rust Installation Methods.
Creating a "Hello, world!" program in Rust is straightforward.
fn main() {
println!("Hello, world!");
}
println!
is a macro, essentially a shorthand for a function. In this case, without using macros, you might have to write longer code. In Rust, you will encounter various macros to expedite code writing. println!
functions similarly to console.log
in JavaScript; it prints everything to the console screen.
Syntax for "building" the program into a binary:
$ rustc main.rs
You can then run the program using:
$ ./main
Hello, world!
To run a Rust program, you first need to compile it into machine code. Unlike JavaScript, where programs execute immediately without compilation, Rust also offers mechanisms for immediate execution, but I won't delve into that in this article.
In Rust, you declare a variable using the let
or const
keyword.
let a: i32 = 1;
# or let the type be inferred
let a = 1; // a: i32
As Rust is a statically typed language, variables need to be declared with a type when initialized, or Rust will automatically infer the type for the first declaration.
By default, Rust variables are immutable, meaning their values cannot be changed. You use the mut keyword to declare mutable variables.
let mut a: i32 = 1;
a = 2;
Functions in Rust must be value-stable. Rust doesn't allow declaring functions with values that change based on variables.
const A: i32 = 1;
# or
const A: i32 = 1 + 2;
# Computing from a variable will result in an error
const A: i32 = a + 1; // error
Basic data types in Rust include char
, string
, integer
, float
, boolean
, and data structures like Tuple
and Array
. For more details, you can refer to Data Types.
Functions in Rust can return data or not. If they return data, you need to declare the return type.
# Function without a return value
fn another_function() {
println!("Another function.");
}
# Function with a return value
fn five() -> i32 {
return 5;
}
# Or use a concise format
fn five() -> i32 {
5
}
# Function with parameters
fn five(n: i32) -> i32 {
n
}
In Rust, ending a line with ;
is mandatory. However, if you don't use return
, the line without ;
is treated as the return value.
In Rust, if...else
acts as an expression, meaning it can return a value or not, similar to functions.
# if...else without returning a value
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
# if...else returning a value that can be assigned to a variable
let condition = true;
let number = if condition { 5 } else { 6 };
When a value is returned, there must always be an else, and the return type must be consistent.
Unlike JavaScript, where evaluation expressions can be data or logical operations, Rust mandates that the expression in if must be a logical expression, meaning it must be either true
or false
.
The basic syntax for looping is to use loop.
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
Additionally, Rust has while
loops.
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
And for
loops.
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
for
can be used with any data type that can be iterated, similar to JavaScript. For example:
for number in (1..4) {
println!("{number}!");
}
(1..4)
is a u32
array structure, so for iterates through each element and executes the statements inside the curly braces.
Struct is a data structure with attributes and associated methods, similar to a Class
in object-oriented programming.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
let user = User {
active: true,
username: String::from("hoaitx"),
email: String::from("[email protected]"),
sign_in_count: 1,
};
Struct also has a syntax similar to Spread syntax in JavaScript for overriding data.
let user2 = {
username: String::from("2coffee"),
email: String::from("[email protected]"),
..user
};
To declare methods for a struct, you use impl.
impl User {
fn sayName(&self) -> String {
self.username
}
fn sayEmail(&self) -> String {
self.email
}
}
let user = User {
active: true,
username: String::from("hoaitx"),
email: String::from("[email protected]"),
sign_in_count: 1,
};
println!("user say name {}", user.sayName);
self
is similar to this in classes, used to refer to its attributes and methods.
When methods don't have self, they are called associated functions. They are used to create a predefined instance of the struct.
impl User {
fn default(size: u32) -> User {
Self {
active: false,
username: String::from(""),
email: String::from(""),
sign_in_count: 0,
}
}
}
let defaultUser = User::default();
This is reminiscent of the String::from()
method.
There are many new concepts here. For example, the String::from()
syntax for creating a string in Rust. Why is it so complicated? In contrast, in JavaScript or many other languages, you simply put them in single ('') or double ("") quotes. To clarify, we need to familiarize ourselves with stack and heap memory in Rust.
Stack memory is used to store fixed-size variable values, such as char
, str
, int32
, int64
, and so on, whereas heap memory, on the contrary, has variable-sized data depending on runtime. Data within double quotes ("") is identified as char
Data within double quotes ("") is str
, while using String:from()
makes it String
and stores it in the heap.
So, in summary, what's the difference between str
and String
? It's primarily in memory management in Rust. Rust lacks automatic garbage collection, so it had to develop memory management for variables, or in other words, the variable's lifetime in the program.
Are all variables immutable by default in Rust? Yes, that means they cannot be changed after declaration. Don't worry; Rust still allows us to declare mutable variables, which is a kind of "102" memory management mechanism. We'll explore this further in the next article!
Hello, my name is Hoai - a developer who tells stories through writing ✍️ and creating products 🚀. With many years of programming experience, I have contributed to various products that bring value to users at my workplace as well as to myself. My hobbies include reading, writing, and researching... I created this blog with the mission of delivering quality articles to the readers of 2coffee.dev.Follow me through these channels LinkedIn, Facebook, Instagram, Telegram.
Comments (1)