DEV Community

Devrata puri
Devrata puri

Posted on

Rust Series: Day 1

Understanding Rust Basics

This guide introduces foundational concepts in Rust, providing insights into key language features and practical examples to solidify your understanding.

Associated Functions and Their Usage

Associated functions are functions tied to a type rather than an instance. They are defined in an impl block and accessed using ::. For example, String::new() creates a new, empty String.

Example:

fn main() {
    let s = String::new();
    println!("String: {}", s); // Output: String: 
}
Enter fullscreen mode Exit fullscreen mode

Rust also allows defining custom associated functions for your types:

struct MyStruct;

impl MyStruct {
    fn new() -> Self {
        MyStruct
    }

    fn describe() {
        println!("This is MyStruct.");
    }
}

fn main() {
    let instance = MyStruct::new(); // Creates a new instance of MyStruct
    MyStruct::describe(); // Prints: This is MyStruct.
}
Enter fullscreen mode Exit fullscreen mode

Type-Level vs Instance-Level Functions

In Rust, there is a distinction between type-level and instance-level functions. Type-level functions operate on the type itself, while instance-level functions operate on specific instances.

Comparison:

Feature Type-Level Instance-Level
Definition Associated with the type itself Associated with an instance of the type
Call Syntax TypeName::function_name() instance_name.method_name()
Access to self No (self is not present) Yes (self, &self, or &mut self)
Example String::new() string_instance.len()

Common Examples of Associated Functions

Rust provides several associated functions for its standard types:

Vectors

fn main() {
    let mut numbers: Vec<i32> = Vec::new(); // Creates an empty vector
    numbers.push(42);
    println!("Numbers: {:?}", numbers); // Output: Numbers: [42]
}
Enter fullscreen mode Exit fullscreen mode

Hash Maps

use std::collections::HashMap;

fn main() {
    let mut scores: HashMap<String, i32> = HashMap::new();
    scores.insert("Alice".to_string(), 10);
    println!("Scores: {:?}", scores); // Output: Scores: {"Alice": 10}
}
Enter fullscreen mode Exit fullscreen mode

Enums: Option and Result

Rust uses enums like Option and Result to handle optional values and errors explicitly and safely.

Option

Option represents a value that may or may not exist. It has two variants:

  • Option::Some(value) for a present value.
  • Option::None for no value.
Example:
fn find_value(values: &[i32], target: i32) -> Option<usize> {
    values.iter().position(|&x| x == target)
}

fn main() {
    match find_value(&[1, 2, 3], 2) {
        Some(index) => println!("Found at index: {}", index),
        None => println!("Not found"),
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Result represents the outcome of an operation that may succeed or fail. It has two variants:

  • Result::Ok(value) for success.
  • Result::Err(error) for failure.
Example:
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10, 2) {
        Ok(result) => println!("Result: {}", result),
        Err(err) => println!("Error: {}", err),
    }
}
Enter fullscreen mode Exit fullscreen mode

Declaring Integers and Default Values

Integers in Rust are primitive types and do not have associated functions like String::new(). However, you can initialize them directly or use the Default trait.

Examples:

let x: i32 = 0; // Default value for i32
let y = u64::default(); // Using `Default` trait for unsigned 64-bit integer
Enter fullscreen mode Exit fullscreen mode

You can mimic String::new() with:

fn main() {
    let x: i32 = i32::default(); // Default value is 0
    println!("x = {}", x); // Output: x = 0
}
Enter fullscreen mode Exit fullscreen mode

Reading Integers with io::stdin()

Rust's io::stdin() is used for reading user input. Since input is read as a string, you must parse it into the desired type, like i32.

Basic Example:

use std::io;

fn main() {
    let mut input = String::new();
    println!("Enter a number:");

    io::stdin()
        .read_line(&mut input)
        .expect("Failed to read line");

    let number: i32 = input.trim().parse().expect("Invalid number");
    println!("You entered: {}", number);
}
Enter fullscreen mode Exit fullscreen mode

With Error Handling:

use std::io;

fn main() {
    let mut input = String::new();
    println!("Enter a number:");

    io::stdin()
        .read_line(&mut input)
        .expect("Failed to read line");

    let number: i32 = match input.trim().parse() {
        Ok(num) => num,
        Err(_) => {
            println!("Invalid input, defaulting to 0");
            0
        }
    };

    println!("You entered: {}", number);
}
Enter fullscreen mode Exit fullscreen mode

What I Learned While Creating a Guessing Game

Creating a simple guessing game in Rust was an excellent way to explore core language features such as input handling, random number generation, and control flow. Here are the key takeaways:

Key Steps in the Guessing Game

  1. Importing Required Modules:

    • Used rand crate for generating random numbers.
    • Used std::io for handling user input.
  2. Generating a Random Number:

   use rand::Rng;

   let secret_number = rand::thread_rng().gen_range(1..=100);
Enter fullscreen mode Exit fullscreen mode
  1. Taking User Input: Read input from the user and parse it into an integer:
   use std::io;

   let mut guess = String::new();
   io::stdin().read_line(&mut guess).expect("Failed to read line");
   let guess: u32 = guess.trim().parse().expect("Please enter a number!");
Enter fullscreen mode Exit fullscreen mode
  1. Using match for Comparison: Compare the guessed number with the secret number using pattern matching:
   match guess.cmp(&secret_number) {
       std::cmp::Ordering::Less => println!("Too small!"),
       std::cmp::Ordering::Greater => println!("Too big!"),
       std::cmp::Ordering::Equal => {
           println!("You win!");
           break;
       }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Error Handling: Added proper error handling for invalid input to ensure the program doesn't crash.

Lessons Learned

  • How to use external crates like rand.
  • Parsing user input and handling errors effectively.
  • Leveraging pattern matching with match for clean control flow.
  • Building a complete program by combining Rust's core concepts.

Conclusion

On Day 1, I explored Rust basics and created a guessing game. This included:

  • Understanding associated functions like String::new.
  • Exploring enums like Option and Result.
  • Using io::stdin() for input handling.
  • Learning practical programming concepts while creating the guessing game.

These exercises laid a strong foundation for further learning in Rust. The journey has just begun!

Top comments (0)