Rust: Rust Variables and Data Types

Last updated: 2026-08-26

In Rust, variables are immutable by default—this is not a limitation, but a safety guarantee.

Rust's variable and type systems are the cornerstone of its commitment to memory safety. Understanding immutability, type inference, and scalar types is the first step toward mastering Rust.


1. What You'll Learn



2. A True Story of a Cashier

(1) The Struggle: Correcting Errors in Numbers in Excel

Lisa is a cashier at a small grocery store. Every day after work, she uses Excel to tally the day's sales:

After she learned Rust, she remarked, "If Excel cells were uneditable by default, I wouldn't have to worry about overwriting formulas."

(2) Rust's Variable Model

RUST
fn main() {
    let total_sales: f64 = 1280.50;   // Immutable by default -- Today's Sales
    // total_sales = 1500.00;         // ❌ Compilation Error: You cannot modify immutable variables!

    let mut daily_changes = 0.0;      // Use mut to declare a mutable variable
    daily_changes = 150.0;            // ✅ Can be modified
    daily_changes = daily_changes + 200.0;

    const TAX_RATE: f64 = 0.08;      // Compile-time constant, Global Fixed
    println!("Tax Rate: {}, Today's Accounts Receivable: {}", TAX_RATE, total_sales * (1.0 + TAX_RATE));
}

Rust's philosophy is: If it can stay unchanged, don't change it. Default immutability makes the code easier to reason about and reduces bugs caused by unintended modifications.



3. Variable Declaration

(1) let: immutable by default

RUST
let x = 5;       // Immutable Variable
// x = 10;       // ❌ Compilation Error: cannot assign twice to immutable variable

(2) let mut: mutable variables

RUST
let mut y = 5;   // Mutable Variable
y = 10;          // ✅ Can be reassigned

(3) const: Compile-time constant

RUST
const MAX_POINTS: u32 = 100_000;   // The type must be specified.
const PI: f64 = 3.1415926535;
Characteristics let let mut const
Variability Immutable Mutable Immutable
Exists at runtime Yes Yes No (inlined at compile time)
Type annotation Optional Optional Required
Global scope No No Yes
Expression Evaluation Runtime Runtime Compile-time constant


4. Scalar Types

Rust has four basic scalar types: integers, floating-point numbers, booleans, and characters.

100%
graph TB
    A[Scalar Types] --> B[Integer: i8/u8/i16/u16/i32/u32/i64/u64/i128/u128]
    A --> C[Floating-point: f32/f64]
    A --> D[Boolean: bool]
    A --> E[Character: char]

(1) Integer Types

Length Signed Unsigned Range
8-bit i8 u8 -128 ~ 127 / 0 ~ 255
16-bit i16 u16 -32768 ~ 32767 / 0 ~ 65535
32-bit i32 u32 ±2.1 billion / 0 ~ 4.2 billion
64-bit i64 u64 ±9.2 × 10¹⁸ / 0 ~ 1.8 × 10¹⁹
128-bit i128 u128 Maximum range
arch isize usize Same as the system's bit width (64-bit system = i64/u64)

The default integer type is i32—it offers the best performance and is sufficient for most needs.

(2) Floating-Point Types

RUST
let a: f32 = 3.14;    // 32-bit precision floating-point (Single Precision)
let b: f64 = 3.141592653589793;  // 64-bit precision floating-point (Double Precision, Default Type)

The default floating-point type is f64—on modern CPUs, f64 and f32 have nearly the same speed, but f64 offers higher precision.

(3) Boolean Type

RUST
let is_ok: bool = true;
let is_not = false;

Boolean values are commonly used in conditional statements, such as the conditional expressions if and while.

(4) Character Types

RUST
let c: char = 'A';       // ASCII Character
let emoji: char = '🦀';  // Unicode Character(4 Byte)
let han: char = 'Rust';    // Unicode character

In Rust, char is a Unicode scalar value that occupies 4 bytes. It is not ASCII! This gives Rust native support for internationalization.

Type Size Value Range Typical Uses
bool 1 byte true / false Conditional checks, flags
char 4 bytes Unicode scalar value Single-character processing
f32 4 bytes ±3.4×10³⁸ (approximately 7-digit precision) Graphics computing, GPU shaders
f64 8 bytes ±1.8×10³⁰⁸ (approximately 15-digit precision) Scientific computing (default floating-point)
i32 4 bytes ±2.1 billion General-purpose integer (default)
u8 1 byte 0–255 Byte data, RGB values
usize arch Same as the system's bit width Array indices, container size


5. Type Inference and Annotation

▶ Example 1: Type Inference (Difficulty ⭐)

Output:

TEXT 📖 Display only
The value of x is: 42
The value of y is: 3.14
RUST
// ============================================
// Rust compiler infers types based on assignments
// ============================================

fn main() {
    let x = 42;           // Inferred as i32 (Default integer type)
    let y = 3.14;         // Inferred as f64 (Default floating-point type)
    let z = true;         // Inferred as bool
    let c = 'R';          // Inferred as char

    // Can use :type to view (Example in This Section, Not a standard method)
    println!("The value of x is: {}", x);
    println!("The value of y is: {}", y);
}

Output:

TEXT 📖 Display only
x The value is: 42
y The value is: 3.14

Output:

TEXT 📖 Display only
small: 9223372036854775807, big: 3.141592653589793, precise: 255, flag: true

The compiler automatically infers types based on context, so developers don't need to specify type annotations everywhere.


▶ Example 2: Explicit Type Annotation (Difficulty ⭐)

Output:

TEXT 📖 Display only
small: 9223372036854775807, big: 3.141592653589793, precise: 255, flag: true
RUST
// ============================================
// Explicitly Specify the Type -- When a specific size or precision is required
// ============================================

fn main() {
let small: u8 = 255;           // u8 Maximum value
let big: i64 = 9_223_372_036_854_775_807;  // An underscore can be used as a digit separator
let precise: f32 = 3.141592653589793;        // f32 May result in loss of precision
let flag: bool = 5 > 3;                      // The result of a Boolean expression

    println!("small: {}, big: {}, precise: {:.10}, flag: {}", small, big, precise, flag);
}

Output:

TEXT 📖 Display only
small: 255, big: 9223372036854775807, precise: 3.1415927410, flag: true

Output:

TEXT 📖 Display only
small: 255, big: 9223372036854775807, precise: 3.1415927410, flag: true

Note that f32 has a precision of only about 7 significant digits, and 3.141592653589793 is truncated to 3.1415927410.


▶ Example 3: Variable Shadowing (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
x value: 12
name length: 4
RUST
// ============================================
// Shadowing: Overwrite a previous variable with the same name
// ============================================

fn main() {
    let x = 5;              // First x
    let x = x + 1;          // Shadowing: Second x, value becomes 6
    let x = x * 2;          // Shadowing: Third x, value becomes 12

    // Shadowing allows for a change in type!
    let name = "Rust";       // &str Type
    let name = name.len();   // became usize Type!

    println!("x value: {}", x);
    println!("name length: {}", name);
}

Output:

TEXT 📖 Display only
x value: 12
name length: 4

Output:

TEXT 📖 Display only
=== Inventory Management System ===

Shadowing ≠ mutability. Shadowing creates a new variable (new memory), while mut modifies the same block of memory. Shadowing can change a variable's type within the same scope, but mut cannot.


▶ Example 4: Comprehensive Exercise—Inventory Management Widget (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
=== Inventory Management System ===
Products: Wireless Mouse
Unit Price: 42.49 yuan yuan
Inventory: 2500 items

--- Shipped Today 300 items ---
Remaining Inventory: 2500 items

--- Member Discount 15% off ---
Member Price: 42.49 yuan yuan
Price Tag: 42.49 yuan

Total Inventory Value: <total_value> yuan
Ample inventory (> 500), No need to restock
Out of Stock! Suggested Reorder 7500 items
RUST
// ============================================
// Comprehensive Example: Using Variables, Shadowing and Constants in Inventory Management
// Demo let/mut/const/shadowing Practical Applications
// ============================================

const MAX_STOCK: u32 = 10_000;
const DISCOUNT_THRESHOLD: u32 = 500;

fn main() {
    let item_name = "Wireless Mouse";
    let price: f64 = 49.99;
    let mut stock: u32 = 2500;

    println!("=== Inventory Management System ===");
    println!("Products: {}", item_name);
    println!("Unit Price: {:.2} yuan", price);
    println!("Inventory: {} items", stock);

    stock = stock - 300;
    println!("\n--- Shipped Today 300 items ---");
    println!("Remaining Inventory: {} items", stock);

    let price = price * 0.85;
    println!("\n--- Member Discount 15% off ---");
    println!("Member Price: {:.2} yuan", price);

    let price = format!("{:.2} yuan", price);
    println!("Price Tag: {}", price);

    let total_value = (stock as f64) * 49.99;
    println!("\nTotal Inventory Value: {:.2} yuan", total_value);

    if stock > DISCOUNT_THRESHOLD {
        println!("Ample inventory (> {}), No need to restock", DISCOUNT_THRESHOLD);
    } else {
        let needed = MAX_STOCK - stock;
        println!("Out of Stock! Suggested Reorder {} items", needed);
    }

    println!("Maximum Capacity: {} items", MAX_STOCK);
}

Output:

TEXT 📖 Display only
=== Inventory Management System ===
Products: Wireless Mouse
Unit Price: 49.99 yuan
Inventory: 2500 items

--- Shipped Today 300 items ---
Remaining Inventory: 2200 items

--- Member Discount 15% off ---
Member Price: 42.49 yuan
Price Tag: 42.49 yuan

Total Inventory Value: 109978.00 yuan
Ample inventory (> 500), No need to restock
Maximum Capacity: 10000 items

This example combines the use of the const constant, the let immutable variable, the let mut mutable variable, and variable shadowing (where the shadowed type changes from f64 to String), demonstrating how these different variable declaration methods work together in a real-world scenario.


▶ Example 5: Integer Overflow and Safe Arithmetic (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
u8 Maximum value: 255
255 + 1 (overflowing_add): <result>, Did overflow?: <did_overflow>
255 + 1 (checked_add): <checked>
255 + 100 (saturating_add): <saturating>
255 + 1 (wrapping_add): <wrapping>
i8 absolute value of minimum: <abs_result>, Did overflow?: <overflow>
RUST
// ============================================
// Integer Overflow Behavior: debug mode panic vs release mode wrapping
// ============================================

fn main() {
    let max_u8: u8 = 255;
    println!("u8 Maximum value: {}", max_u8);

    // Safe Computation Method (No panic, Returns overflow result)
    let (result, did_overflow) = max_u8.overflowing_add(1);
    println!("255 + 1 (overflowing_add): {}, Did overflow?: {}", result, did_overflow);

    let checked = max_u8.checked_add(1);
    println!("255 + 1 (checked_add): {:?}", checked);

    let saturating = max_u8.saturating_add(100);
    println!("255 + 100 (saturating_add): {}", saturating);

    let wrapping: u8 = max_u8.wrapping_add(1);
    println!("255 + 1 (wrapping_add): {}", wrapping);

    let neg_i8: i8 = -128;
    let (abs_result, overflow) = neg_i8.overflowing_abs();
    println!("i8 absolute value of minimum: {}, Did overflow?: {}", abs_result, overflow);
}

Output:

TEXT 📖 Display only
Original Input: Result: 84 (Type: &str)
Analysis Results: Result: 84
After unpacking: Result: 84 (Type: f64)
Round to an integer: Result: 84 (Type: i32, Truncation of Decimals)
After doubling: Result: 84 (Type: i32)
Final Output: Result: 84 (Type: String)
Alice: Score=95, Level=<alice_grade>




In Rust, integer overflow causes a panic in debug mode, but results in a wrap-around in release mode. It is recommended to use the checked_*, saturating_*, and wrapping_* methods to explicitly control overflow behavior.


▶ Example 6: Practical Application of Variable Shadowing and Type Conversion (Difficulty: ⭐⭐⭐)

Output:

TEXT 📖 Display only
Original Input: Result: 84 (Type: &str)
Analysis Results: Result: 84
After unpacking: Result: 84 (Type: f64)
Round to an integer: Result: 84 (Type: i32, Truncation of Decimals)
After doubling: Result: 84 (Type: i32)
Final Output: Result: 84 (Type: String)
Alice: Score=95, Level=<alice_grade>
RUST
// ============================================
// Practical Examples of Variable Shadowing: String Parsing Chain
// ============================================

fn main() {
    let input = "42.5";
    println!("Original Input: {} (Type: &str)", input);

    let input = input.parse::<f64>();
    println!("Analysis Results: {:?}", input);

    let input = match input {
        Ok(value) => value,
        Err(_) => 0.0,
    };
    println!("After unpacking: {} (Type: f64)", input);

    let input = input as i32;
    println!("Round to an integer: {} (Type: i32, Truncation of Decimals)", input);

    let input = input * 2;
    println!("After doubling: {} (Type: i32)", input);

    let input = format!("Result: {}", input);
    println!("Final Output: {} (Type: String)", input);

    let alice_score = "95";
    let alice_score = alice_score.parse::<u32>().unwrap_or(0);
    let alice_grade = if alice_score >= 90 { "A" } else { "B" };
    println!("Alice: Score={}, Level={}", alice_score, alice_grade);
}

Output:

TEXT 📖 Display only
Original Input: 42.5 (Type: &str)
Analysis Results: Ok(42.5)
After unpacking: 42.5 (Type: f64)
Round to an integer: 42 (Type: i32, Truncation of Decimals)
After doubling: 84 (Type: i32)
Final Output: Result: 84 (Type: String)
Alice: Score=95, Level=A

Variable shadowing allows for a gradual conversion of data types within the same scope—from &strResult<f64>f64i32String—using the same variable name at each step, resulting in clear, redundancy-free code.


❓ FAQ

Q What is the difference between i32 and u32?
A Values starting with "i" are signed (can be positive or negative), while those starting with "u" are unsigned (can only be non-negative).
Q Why is the default in Rust i32 instead of i64?
A Performance trade-offs.
Q What is the difference between shadowing and let mut?
A Shadowing creates a new variable (with a different type), while mut modifies the same variable (without changing its type).
Q A char takes up 4 bytes, so aren't strings a waste of space?
A Rust strings are not arrays of char.
Q What is the difference between const and let?
A const is inlined at compile time, while let is allocated at runtime.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Declare a variable of type u16 and assign it the value 65535, then try to change it to 65536 and observe the compiler's error message.
  2. Difficulty ⭐⭐: Write a program that uses let mut to declare a variable that accumulates the sum of numbers from 1 to 5, and output the result.
  3. Difficulty ⭐⭐⭐: Try declaring a const constant outside of fn main() and using it inside the function. Then try declaring a let variable outside the function and observe the compiler error—this will help you understand the global scope of const.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏