Shanraq.org Shanraq.org
Variables, values and types
IT

Rust: from zero to your own organizer Lesson 9 of 10

Variables, values and types

Predict values and distinguish reassignment from a new binding.

This text was translated with AI.

Prerequisite: lessons 1–8. Work in organizer unless stated otherwise.

Why this matters

A value is concrete data such as 2. A variable associates a name with a value. Think of a labelled place for data. Later we will see the limit of that image: a name does not always mean a separate box with an independent copy.

A type determines valid values and operations. Think of rules for the contents: numbers support arithmetic; text supports text operations. A name alone does not determine its type.

The whole picture

Replace src/main.rs, save and use cargo run from organizer:

fn main() {
    let mut count: u32 = 2;
    println!("Before: {count}");
    count = 3;
    println!("After: {count}");
}

Output:

Before: 2
After: 3

Reading the declaration

let declares a variable. mut permits assigning a new value. count is our chosen name. : u32 is a type annotation, explicitly selecting a 32-bit unsigned integer. Unsigned means no negative values. A bit is a binary unit with two possible states; the number of bits limits the range. Lesson 10 gives the exact ranges. = assigns a value; it does not ask whether values are equal. 2 is a numeric literal and ; ends the statement.

Inside the println! text, {count} inserts the variable’s value. These braces inside a string are not a Rust block. count = 3 changes the value without changing the name or type. Without mut, reassignment is forbidden.

Inferred types

An annotation can sometimes be omitted: the compiler determines the type from the value and surrounding uses. This is type inference, not the absence of a type. With let count = 2 and no other constraints, the integer type defaults to i32, which permits negative numbers. We explicitly use u32 for this teaching counter rather than asking you to guess the context.

true and false have type bool, representing truth values. ‘A’ has type char, one Unicode scalar value: one permitted code point in a common system for representing characters. A visible character can require several such values; we will revisit this with Unicode. “Rust” is a string literal of type &str, a reference to text, not a growable string. Single and double quotes mean different things. We will explore text ownership in lesson 22 and Boolean operations in lesson 11; here the point is that types describe more than numbers.

Try several types in one small program. true is printed as true; Rust does not translate Boolean values. ready, mark and title are our chosen names. Braces inside the println! string insert a named value, just as {count} did above.

fn main() {
    let ready: bool = true;
    let mark: char = 'A';
    let title: &str = "Rust";
    println!("{ready}");
    println!("{mark}");
    println!("{title}");
}
true
A
Rust

A new declaration and scope

Another let with the same name creates a new binding called shadowing; it is not reassignment. Scope is the part of the program where a name is visible. A nested block may use its own binding, with the outer one visible again afterwards:

fn main() {
    let count = 2;
    {
        let count = 3;
        println!("Inside: {count}");
    }
    println!("Outside: {count}");
}

Output:

Inside: 3
Outside: 2

const declares a constant, evaluated during compilation. Its type must be written explicitly; mut is not allowed. An immutable variable can receive a value during execution, so these are not the same concept. Constant names conventionally use uppercase letters and underscores. DAILY_LIMIT means a daily limit.

const DAILY_LIMIT: u32 = 5;

fn main() {
    println!("Plan: {DAILY_LIMIT}");
}

Output:

Plan: 5

Deliberate error: we forgot permission to change the variable. Expect E0384 rather than output:

fn main() {
    let count = 2;
    println!("Before: {count}");
    count = 3;
    println!("After: {count}");
}

Recall map

Value → name → type → permitted actions. let creates a binding; mut allows reassignment; another let shadows; blocks delimit scope; const declares a constant.

Warm-up

  1. Predict the outer count after the nested block.
  2. Complete let ___ count: u32 = 4; so it may later be assigned 5.
  3. Repair E0384 without replacing the variable with a constant.

Exercise

Required. Create a mutable u32 counter set to 4, print “Before: 4”, assign 5 and print “After: 5”. Explain why this is not shadowing.

Your own data. Choose two different small nonnegative numbers.

Hint and reference answer

The reference has only one let followed by reassignment. Warm-up answers: the outer value stays 2; the missing word is mut; permission to reassign fixes the deliberate error. Repeat the two examples separately if the distinction is still unclear before moving to arithmetic.

fn main() {
    let mut count: u32 = 4;
    println!("Before: {count}");
    count = 5;
    println!("After: {count}");
}

Expected output:

Before: 4
After: 5

Previous lesson · Contents · Next lesson

If you have found a mistake or a typo in this article, tell us about it

Comments (0)

No comments yet. Be the first.