Basic Data

1. Welcome and Icebreaker:

Welcome back!

If you could create a new data type to represent anything you wanted, what would it be, and what kind of values would it hold?

2. Outline the Session:

Here’s our plan for this session:

  1. Concept Explanation: We’ll discuss the common scalar and compound data types in Rust.
  2. Live Coding Demonstration: We’ll write code examples to demonstrate how to declare and use variables with different types.
  3. Tasks: I will challenge you in task section.

Learning Outcomes: By the end of this session, you will be able to:

  • Understand and use Rust’s basic data types (integers, floats, booleans, characters, strings, tuples, arrays).
  • Declare variables using let, and understand mutability with mut.
  • Understand type inference and explicit type annotations.

Expectations: Don’t hesitate to ask questions! Experimenting with the code is highly encouraged.

3. Concept Explanation:

Rust is a statically typed language. This means that the compiler must know the type of every variable at compile time. However, the compiler is often smart enough to infer the type based on how you use the variable. You can also explicitly specify the type if you wish.

Here are some of the core data types in Rust:

  • Scalar Types: Represent a single value.
    • Integers: Whole numbers. Rust provides signed (can be negative) and unsigned (non-negative) integers of various sizes:
      • i8, i16, i32, i64, i128: Signed integers (8, 16, 32, 64, and 128 bits, respectively).
      • u8, u16, u32, u64, u128: Unsigned integers.
      • isize, usize: Their size depends on the architecture of your computer (usually 32 or 64 bits). Used for indexing collections.
    • Floating-Point Numbers: Numbers with decimal points.
      • f32: 32-bit floating-point number (single precision).
      • f64: 64-bit floating-point number (double precision). This is the default for floating-point literals.
    • Booleans: Represent truth values.
      • bool: Can be either true or false.
    • Characters: Represent a single Unicode character.
      • char: Enclosed in single quotes (e.g., 'a', 'Z', '💖').
  • Compound Types: Group multiple values into one type.
    • Tuples: A fixed-size collection of values, where each value can have a different type.
    • Arrays: A fixed-size collection of values, where each value must have the same type.
  • String Types: Represent text.
    • &str: String slice (we’ll discuss this in detail in the next section).
    • String: Growable string.

Variable Declarations:

  • We use the let keyword to declare a variable.
  • By default, variables in Rust are immutable (their values cannot be changed after they are initialized).
  • To make a variable mutable, we use the mut keyword.

4. Live Coding Demonstration:

fn main() {
    // Integers
    let x: i32 = -10; // Explicit type annotation (i32)
    let y = 20;      // Type inference (i32 by default)
    let z: u8 = 255;  // Unsigned 8-bit integer

    // Floating-point numbers
    let a = 2.5;      // Type inference (f64 by default)
    let b: f32 = 3.14; // Explicit type annotation (f32)

    // Booleans
    let is_active = true;
    let is_finished: bool = false;

    // Characters
    let letter = 'A';
    let emoji: char = '🚀';

    // Tuples
    let tup: (i32, f64, char) = (-5, 4.2, 'Z');
    let (t1, t2, t3) = tup; // Destructuring a tuple
    println!("Tuple values: {}, {}, {}", t1, t2, t3);
    println!("Tuple.0 values: {}, Tuple.1 values: {}, Tuple.2 values: {} ", tup.0,tup.1,tup.2);

    // Arrays
    let arr: [i32; 5] = [1, 2, 3, 4, 5]; // Array of 5 i32 integers
    let first = arr[0]; // Accessing an element by index (0-based)
     println!("Array values: {}, {}, {}, {}, {}", arr[0],arr[1],arr[2],arr[3],arr[4]);
    // Mutability
    let mut count = 0;  // Mutable variable
    count = count + 1;
     println!("Count values: {}",count);

    // Shadowing
    let my_variable = 5;
    let my_variable = my_variable + 3; // Re-declaring with a new value
    let my_variable = "Now it is String"; // Re-declaring with a String
    println!("Shadowing values: {}",my_variable);
}

Key takeaways from the code:

  • Type Annotations: We can explicitly specify the type using a colon : followed by the type name (e.g., let x: i32 = -10;).
  • Type Inference: If we don’t provide a type annotation, Rust tries to infer the type based on the value (e.g., let y = 20; infers i32).
  • Mutability: let mut count = 0; creates a mutable variable that we can change later.
  • Tuple Access: We can access tuple elements by their index, starting from 0 (e.g., tup.0, tup.1). We can also destructure a tuple to assign its elements to individual variables.
  • Array Access: Arrays are also accessed using 0-based indexing (e.g., arr[0]).
  • Array Declaration: The type of array show with [i32; 5], first part is type of element and after semicolon fixed size.
  • Shadowing: Shadowing lets you reuse a variable name within the same scope.

5. Tasks

  1. Experiment with Types: Create variables of different integer types (i8, u16, i64, etc.) and assign them values near the boundaries of their ranges (e.g., the maximum value for u8 is 255). See what happens if you try to assign a value that is too large or too small for the type.
  2. Tuple Manipulation: Create a tuple containing a string, a boolean, and a floating-point number. Then, access and print each element of the tuple individually.
  3. Array Practice: Declare an array of f64 values with a length of 3. Initialize it with some floating-point numbers. Then, write code to calculate and print the sum of the elements in the array.
  4. Mutability Challenge: Declare a mutable variable. Perform several arithmetic operations on it (add, subtract, multiply), and print its value after each operation.
  5. Shadowing Challenge: Declare a immutable variable with number type. Then Shadowing this variable and give string type.