S

Sajiron

19 min readPublished on Feb 17, 2025

Rust Basics: Syntax, Data Types, and Naming Conventions

DALL·E 2025-02-17 19.15.52 - A cutting-edge digital artwork illustrating Rust programming basics. The image showcases a sleek futuristic coding terminal with glowing Rust code sni.webp

1. Introduction

Related Post

📖 If you haven't read it yet, check out the previous blog: Rust Programming: A Beginner’s Guide to Getting Started

Understanding Rust’s basic syntax and data types is essential for writing efficient and safe code. In this post, we’ll explore how Rust handles variables, mutability, data types, and naming conventions.

2. Working with Variables

Immutable Variables (Default)

In Rust, variables are immutable by default. This means once a variable is assigned a value, it cannot be changed.

fn main() {
let x = 10;
println!("Value of x: {}", x);
// x = 20; // This will cause a compile-time error
}

Mutable Variables

To make a variable mutable, use the mut keyword:

fn main() {
let mut y = 10;
println!("Before mutation: {}", y);
y = 20;
println!("After mutation: {}", y);
}

Constants (const) vs Immutable Variables (let)

Rust provides two ways to declare immutable values: const and let. While both prevent modification after assignment, they have fundamental differences.

A const variable is always immutable, requires explicit type annotation, and is evaluated at compile time. const variables are stored directly in the program’s binary, making them available globally across the entire program. They cannot be shadowed or reassigned.

A let variable, on the other hand, is also immutable by default but allows shadowing. If mutability is needed, you can explicitly declare the variable as mut, allowing it to be reassigned instead of shadowed. Shadowing enables transformations of the same variable name while keeping it immutable. However, if you need to update a variable frequently, using mut is a better option than shadowing. Unlike const, let variables are evaluated at runtime and do not require explicit type annotations. They exist within the scope they are defined and are removed once the scope ends.

Constants are always immutable and must have their types explicitly specified:

const MAX_USERS: u32 = 1000;

Type Inference, and Annotation

Rust provides type safety via static typing, meaning that every variable has a known type at compile time. Variables can be explicitly type annotated, but in most cases, the Rust compiler can infer the type from the context, reducing the need for explicit annotations.

fn main() {
let number = 42; // Compiler infers `i32`
let float_number: f64 = 3.14; // Explicit type annotation
println!("{} and {}", number, float_number);
}

Scope, Shadowing, and Freezing

Scope: Variables in Rust are limited to the block {} in which they are declared. Once a variable goes out of scope, it is no longer accessible.

fn main() {
let outer_var = 10; // Outer scope variable

{
let inner_var = 20; // Inner scope variable
println!("Inner variable: {}", inner_var);
println!("Outer variable from inner scope: {}", outer_var);
}

// println!("Inner variable: {}", inner_var); // ❌ Error: inner_var is out of scope
println!("Outer variable: {}", outer_var); // ✅ Allowed
}

Shadowing: Shadowing in Rust allows a new variable to be declared with the same name, effectively hiding the old variable within a certain scope. Unlike mut, which modifies a variable, shadowing creates a new variable binding, allowing transformations or type changes.

fn main() {
let x = 5; // First x
let x = x + 1; // Shadows the first x
let x = x * 2; // Shadows the second x
println!("Final x: {}", x); // Output: 12

let x = "hello"; // x is now a string
println!("x = {}", x);
}

Freezing: When a mutable variable is shadowed by an immutable one, the original mutable variable becomes frozen and cannot be modified in that scope.

fn main() {
let mut y = 10;
{
let y = y; // Shadows the mutable `y` with an immutable one
// y = 20; // ❌ Compile-time error: `y` is frozen
println!("Frozen y: {}", y);
}
y = 30; // ✅ Allowed: Outer `y` is still mutable
println!("Updated y: {}", y);
}

3. Scalar Types vs Compound Types

Rust categorizes its data types into scalar types and compound types based on their structure and complexity.

1️⃣ Scalar Types

A scalar type represents a single value. Rust has four primary scalar types:

Integer types (i8, i16, i32, i64, i128, u8, u16, u32, u64, u128)

Floating-point types (f32, f64)

Boolean type (bool)

Character type (char)

2️⃣ Compound Types

A compound type groups multiple values together into a single unit. Rust has two primary compound types:

Tuples (Fixed-size collection of values of different types)

fn main() {
let data = (true, 42, "Rust", 3.14);

println!("Boolean: {}", data.0);
println!("Integer: {}", data.1);
println!("String: {}", data.2);
println!("Float: {}", data.3);
}

Arrays (Fixed-size collection of values of the same type)

fn main() {
let numbers = [10, 20, 30, 40, 50]; // An array of integers

println!("Array: {:?}", numbers);
println!("First element: {}", numbers[0]);
println!("Third element: {}", numbers[2]);

// Accessing an element dynamically
let index = 4;
println!("Element at index {}: {}", index, numbers[index]);
}

Differences Between Tuples and Arrays

Feature

Tuple

Array

Data Types

Can hold multiple different types (e.g., (i32, f64, &str))

Must hold elements of the same type (e.g., [i32; 3])

Size

Fixed-size, defined at compile time

Fixed-size, defined at compile time

Access

Access via dot notation (tuple.0, tuple.1)

Access via indexing (array[0], array[1])

Mutability

Immutable by default, but can be mutable (mut)

Immutable by default, but can be mutable (mut)

Memory Layout

Stored in memory as a single unit

Stored sequentially in memory

Use Case

Best for grouping related but different data (e.g., ("Alice", 30, 5.7))

Best for storing multiple values of the same type (e.g., [10, 20, 30])

Iteration

Cannot iterate directly

Can iterate using for loops

4. Type Casting and Type Aliasing

Casting Literals in Rust

Rust does not allow implicit type conversion, so explicit casting using the as keyword is required.

Example: Casting an Integer to a Floating Point

fn main() {
let x: i32 = 10;
let y: f64 = x as f64 + 5.5;
println!("y: {}", y);
}

Example: Casting a Float to an Integer

fn main() {
let num: f64 = 9.8;
let int_num: i32 = num as i32; // Truncates the decimal part
println!("Converted: {}", int_num);
}

Type Aliasing in Rust

Type aliasing allows you to create custom names for existing types, improving readability.

Defining a Type Alias

type Kilometers = i32;

fn main() {
let distance: Kilometers = 100;
println!("Distance: {} km", distance);
}

Using Aliases with Complex Types

type Point = (i32, i32);

fn main() {
let origin: Point = (0, 0);
println!("Origin: ({}, {})", origin.0, origin.1);
}

5. Naming Conventions in Rust

Consistent naming conventions improve code readability and maintainability. Rust follows specific naming rules for different elements, including variables, functions, constants, structs, and modules. Some of the concepts mentioned here, such as lifetimes, generics, and modules, will be discussed in detail in later blogs.

Consistent naming conventions improve code readability and maintainability. Rust follows specific naming rules for different elements, including variables, functions, constants, structs, and modules.

1️⃣ Variables & Functions

Use snake_case (lowercase words separated by underscores) for variable and function names.

This makes them easy to read and aligns with Rust's standard conventions.

let user_name = "Alice";
fn get_user() {}

2️⃣ Constants & Static Variables

Use SCREAMING_SNAKE_CASE for constants and static variables.

Constants are declared using const, while static variables use static.

const MAX_CONNECTIONS: u32 = 100;
static SERVER_PORT: u16 = 8080;

3️⃣ Structs, Enums, and Traits

Use PascalCase (capitalizing each word) for struct, enum, and trait names.

struct UserProfile {
name: String,
age: u8,
}

enum Status {
Active,
Inactive,
}

4️⃣ Modules

Use snake_case for module names.

mod user_profile;

5️⃣ Lifetimes

Use short, lowercase names like 'a, 'b for lifetimes.

fn lifetime_example<'a>(input: &'a str) -> &'a str {
input
}

6️⃣ Generic Type Parameters

Use single uppercase letters for generic types.

Common conventions:

T for a generic type.

E for an error type.

K and V for key-value pair types.

struct Container<T> {
item: T,
}

Following these naming conventions ensures consistency and improves collaboration within Rust projects.

6. Next Steps

Would you like to explore more Rust concepts? Stay tuned for the next tutorial! Now that you understand Rust’s syntax and data types, the next tutorial will cover Control Flow & Functions in Rust.