HackIndia Logo
HackathonsNewsSkillsGrowth
HackIndia LogoContact: +91 9910125804

General

  • Companies
  • Colleges
  • Board of Advisors
  • Brand Ambassadors
  • FAQ
  • Contact
  • Report a Bug

Hackathons

  • Upcoming Hackathons
  • Judges
  • Mentors
  • Partners
  • Participating Colleges
  • Testimonials

Resources

  • About Us
  • Branding
  • Latest News
  • Growth
  • Skills
  • Builders
  • Ecosystem
  • Forum
  • Ideas
  • Prompt Optimizer
  • Games

© 2026 HackIndia. All rights reserved.

Privacy PolicyTerms of ServiceCode of ConductSitemap

Build 2.139 · 05 Nov 2026, 10:27 UTC

    HackIndia
    HackathonsNewsInternshipsSkillsGrowthPro Perks
    ← Ship a Rust CLI project

    All the reading

    Rust

    7 lessons, about 29 minutes. The check's questions come from these.

    Contents
    Compiler errors and ownership
    1. 1. Make the compiler your teammate
    2. 2. Own values without confusion
    Missing values and command flow
    1. 3. Handle absence and failure cleanly
    2. 4. Match data into actions
    Shared behaviour and project setup
    1. 5. Share behaviour with traits
    2. 6. Use crates without chaos
    3. 7. Build and submit the repo

    Compiler errors and ownership

    1. Make the compiler your teammate

    4 min

    In 30 seconds
    • Create a Rust CLI project with Cargo and run it locally.
    • Read compiler errors as instructions, not as failure.
    • Use cargo check, cargo run, and rustc --explain while building.
    Key point

    One idea

    Rust feels strict because the compiler checks your code before users hit bugs. Treat each error as a small task list: where it happened, what Rust expected, and what you can change.

    What Cargo gives you

    Cargo is Rust’s project tool. It creates the folder, stores project settings, builds your code, runs it, and later helps you add crates.

    For your HackIndia submission, Cargo matters because reviewers can clone your repo and run the same commands you ran.

    Your to-do

    Start a CLI project

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    cargo new rust-attendance-cli
    cd rust-attendance-cli
    cargo run
    cargo check
    Example

    First useful output

    Open src/main.rs and replace the starter code. Keep it simple for now. The goal today is to see the edit-check-run loop.

    fn main() {
        let course = "Data Structures";
        let present = 42;
    
        println!("{} attendance count: {}", course, present);
    }
    Your to-do

    2. Own values without confusion

    4 min

    In 30 seconds
    • Know when Rust moves a value and when it borrows it.
    • Use references so your CLI can read data without losing ownership.
    • Fix common ownership errors by changing function parameters.
    Key point

    One thing

    In Rust, each value has one owner. Passing a String into a function moves ownership unless you pass a reference with &.

    For your CLI, this means you should borrow text when you only need to read it. Own it when you need to store it or change it.

    Why this matters

    A CLI reads arguments, file paths, task names, notes, or amounts. These are often Strings. If you move them too early, you cannot use them later for printing, matching, or saving.

    This is not Rust being difficult. It is Rust making memory rules visible. Once you see moves and borrows, compiler errors become useful directions.

    Example

    Move versus borrow

    This version moves name into print_name. After that, main cannot use name again.

    fn print_name(name: String) {
        println!("Project: {}", name);
    }
    
    fn main() {
        let name = String::from("fees-tracker");
        print_name(name);
        println!("Running {}", name); // error: name was moved
    }

    Missing values and command flow

    3. Handle absence and failure cleanly

    4 min

    In 30 seconds
    • Use Option when a value may be missing, like an optional CLI argument.
    • Use Result when an action can fail, like reading a file or parsing input.
    • Return errors with ? so your CLI stays clean and honest.
    Key point

    One clean split

    Use Option for absence. Use Result for failure.

    If nothing went wrong but a value may not exist, choose Option. If something tried to happen and can fail, choose Result.

    Why this matters

    A CLI always meets messy input. A user may skip an argument. A file path may be wrong. A number may not parse.

    Rust makes you handle these cases before your project reaches the demo table. That is good. Your tool should print a clear message, not crash with a confusing panic.

    Example

    Option for missing input

    Here is a tiny CLI pattern using std::env::args. The first real argument may be missing, so nth(1) returns Option.

    Shared behaviour and project setup

    5. Share behaviour with traits

    4 min

    In 30 seconds
    • Use a trait to give the same behaviour to different Rust types.
    • Call trait methods from your CLI code without caring about the exact type.
    • Add one trait-based feature your HackIndia repo can prove.
    Key point

    One idea

    A trait is a shared promise. It says: any type that implements me will provide these methods.

    Use traits when your CLI has different things that should behave in the same way.

    Why traits matter

    Your Rust CLI will grow. Today it may print one kind of report. Tomorrow it may print tasks, expenses, marks, links, or UPI splits.

    Instead of writing separate print logic everywhere, define the behaviour once as a trait. Then each type decides how it does that behaviour.

    Example

    Trait example

    Here is a small CLI-style example. It has two different types, but both can be shown as a user-facing line.

    10 questions, 7 to pass.

    Sign in to take the test

    Use the loop

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    cargo check
    cargo run
    Common mistake

    Do not fight every line

    Beginners often try to fix all errors at once. That creates more confusion.

    Start with the first real error in your file. Later errors may disappear after the first fix.

    Example

    Read an error

    Try this broken code for one minute. It is safe. You are practising how to read the compiler.

    fn main() {
        let marks = 88
        println!("Marks: {}", marks);
    }
    
    // Run:
    // cargo check
    
    // You should see an error near the missing semicolon.
    // Fix it:
    // let marks = 88;
    Tip

    Ask for help well

    When you use ChatGPT, Gemini, or Claude, paste the exact error and the small code block around it. Ask for the smallest fix and the reason.

    Check the answer yourself. It should explain the compiler message, not rewrite your full project without asking.

    Prompt:
    I am learning Rust. Explain this compiler error in simple words.
    Give the smallest fix only.
    Here is my code and error:
    <paste code and cargo check output>
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    git init
    git add .
    git commit -m "Start Rust CLI project"
    Example

    Borrow instead

    If the function only reads the value, take &str or &String. Prefer &str for text input because it works with String and string literals.

    fn print_name(name: &str) {
        println!("Project: {}", name);
    }
    
    fn main() {
        let name = String::from("fees-tracker");
        print_name(&name);
        println!("Running {}", name); // works
    }
    Your to-do

    Choose the parameter

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Common mistake

    Cloning too early

    Beginners often add .clone() everywhere to silence errors. It works, but it hides the real ownership issue and can waste memory.

    Clone only when you truly need two owned copies. In most CLI code, a reference is enough for printing, checking, parsing, or matching.

    let command = String::from("add");
    
    // Usually avoid this:
    show_command(command.clone());
    
    // Prefer this when show_command only reads:
    show_command(&command);
    Tip

    Use AI carefully

    When an AI tool gives you Rust code, ask it: "Which functions should take &str instead of String, and why?" Then check the answer against the compiler.

    Do not accept fixes that only add clone everywhere. Ask for the smallest ownership change that keeps the value usable later.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    match forces you to handle both cases.

    use std::env;
    
    fn main() {
        let name = env::args().nth(1);
    
        match name {
            Some(value) => println!("Hello, {value}"),
            None => println!("Usage: cargo run -- <name>"),
        }
    }
    Example

    Result for failure

    Reading a file can fail. The file may not exist, or your program may not have permission.

    Return Result from a function, then use ? to pass the error upward instead of writing a match every time.

    use std::fs;
    use std::error::Error;
    
    fn read_notes(path: &str) -> Result<String, Box<dyn Error>> {
        let content = fs::read_to_string(path)?;
        Ok(content)
    }
    
    fn main() -> Result<(), Box<dyn Error>> {
        let content = read_notes("notes.txt")?;
        println!("{content}");
        Ok(())
    }
    Common mistake

    Avoid panic habits

    Beginners often use unwrap() because it makes the compiler quiet. It also makes your CLI crash when input is missing or invalid.

    For a hackathon demo, unwrap() is risky. Use match for a friendly message, or return Result and use ? for real failures.

    Tip

    Ask AI carefully

    If you use ChatGPT or Gemini, ask for code that avoids unwrap() and returns Result from main. Then check the answer yourself.

    Look for three things: Option is matched, fallible work returns Result, and ? is only used inside a function that returns Result or Option.

    Prompt:
    Convert this Rust CLI code to handle missing arguments and file read errors without unwrap(). Use Option, Result, match, and ? where suitable.
    Your to-do

    Pick the type

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    4. Match data into actions

    4 min

    In 30 seconds
    • Turn user input and command names into clear Rust actions.
    • Use match arms for strings, numbers, Option, and Result values.
    • Keep CLI branching readable without long if else chains.
    Key point

    One idea

    match takes a value and maps each possible shape to an action. In a CLI, that value is often a command, a flag, or parsed input.

    Use match when the next step depends on clear cases: add, list, done, invalid command.

    Why it matters

    Your CLI will receive messy input. A hostel expense tool may get add, split, total, or a typo like totla.

    Instead of scattering if else checks across the file, put the decision in one match. It becomes easier to read during a hackathon demo and easier to change later.

    Example

    Match a command

    This example reads the first argument after the program name. Then it maps that command to an action.

    You are not building the whole app here. Focus on how the branches stay explicit.

    use std::env;
    
    fn main() {
        let args: Vec<String> = env::args().collect();
    
        let command = args.get(1);
    
        match command {
            Some(cmd) if cmd == "add" => println!("Add a new item"),
            Some(cmd) if cmd == "list" => println!("Show all items"),
            Some(cmd) if cmd == "done" => println!("Mark an item done"),
            Some(other) => println!("Unknown command: {}", other),
            None => println!("Usage: tasker add|list|done"),
        }
    }
    Your to-do

    Read the match

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Tip

    Prefer exact shapes

    When possible, match on simple values. For command strings, you can convert Option into Option so your arms look cleaner.

    This reduces guard clauses like if cmd == "add".

    use std::env;
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let command = args.get(1).map(String::as_str);
    
        match command {
            Some("add") => println!("Add a new item"),
            Some("list") => println!("Show all items"),
            Some("done") => println!("Mark an item done"),
            Some(other) => println!("Unknown command: {}", other),
            None => println!("Usage: tasker add|list|done"),
        }
    }
    Common mistake

    Do not skip cases

    Rust match must be exhaustive. That means every possible value needs a branch.

    Beginners often match only add, list, and done. The compiler then complains because it also sees missing input and unknown input. Add a fallback arm instead of fighting the compiler.

    Example

    Match parsed input

    A CLI often receives text and needs a number. You can match the parse result and choose what to do.

    This keeps error handling close to the input that caused it.

    fn handle_done(input: &str) {
        match input.parse::<usize>() {
            Ok(id) => println!("Mark task {} as done", id),
            Err(_) => println!("Please give a number, like: tasker done 2"),
        }
    }
    
    fn main() {
        handle_done("2");
        handle_done("two");
    }
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    cargo run -- add
    cargo run -- list
    cargo run -- done 2
    cargo run -- done two
    cargo run
    trait Summary {
        fn summary(&self) -> String;
    }
    
    struct Task {
        title: String,
        done: bool,
    }
    
    struct Expense {
        label: String,
        amount: u32,
    }
    
    impl Summary for Task {
        fn summary(&self) -> String {
            match self.done {
                true => format!("[done] {}", self.title),
                false => format!("[todo] {}", self.title),
            }
        }
    }
    
    impl Summary for Expense {
        fn summary(&self) -> String {
            format!("{}: ₹{}", self.label, self.amount)
        }
    }
    
    fn print_summary(item: &impl Summary) {
        println!("{}", item.summary());
    }
    
    fn main() {
        let task = Task {
            title: String::from("Submit HackIndia repo"),
            done: false,
        };
    
        let expense = Expense {
            label: String::from("Canteen chai"),
            amount: 15,
        };
    
        print_summary(&task);
        print_summary(&expense);
    }

    Read the shape

    trait Summary defines the method name. impl Summary for Task gives Task its version. impl Summary for Expense gives Expense its version.

    print_summary takes &impl Summary. That means it accepts any value that implements Summary. It does not need to know whether the value is a Task or an Expense.

    Common mistake

    Common mistake

    Do not put data fields inside a trait. A trait is not a struct.

    Keep data in structs. Keep shared behaviour in traits. Then connect them with impl TraitName for StructName.

    Tip

    Use AI carefully

    You can ask an AI tool: “Suggest one useful Rust trait for my CLI project and show how two structs can implement it.”

    Check that the generated code has trait, struct, impl Trait for Struct, and a function that accepts &impl Trait or a generic trait bound. Also check it still compiles with cargo run.

    Your to-do

    When to add one

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    6. Use crates without chaos

    4 min

    In 30 seconds
    • Pick crates that solve one clear CLI problem.
    • Add dependencies in Cargo.toml without breaking your project.
    • Use a crate in a small Rust CLI feature you can explain.
    Key point

    One crate, one job

    A crate is a Rust package you can reuse. Use it when it saves boring work, like parsing CLI flags, reading dates, or making coloured output.

    Do not add crates because they look popular. For your internship repo, you should be able to explain why each dependency exists.

    What Cargo does

    Cargo reads your Cargo.toml file, downloads dependencies, builds them, and records exact versions in Cargo.lock.

    For an app or CLI project, commit Cargo.lock. It helps another builder, mentor, or recruiter run the same dependency versions you used.

    Your to-do

    Choose a crate

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Add clap

    clap is commonly used for parsing command-line arguments. Here is a small setup for a todo-style CLI.

    This example keeps the dependency simple. It uses clap with its derive feature.

    # Cargo.toml
    [package]
    name = "rust-todo-cli"
    version = "0.1.0"
    edition = "2021"
    
    [dependencies]
    clap = { version = "4.5", features = ["derive"] }
    Example

    Use the crate

    This code lets your CLI accept an action and a task. The pattern matching is still your code. The crate only handles argument parsing.

    Run it with cargo run -- add "pay hostel mess bill".

    use clap::{Parser, Subcommand};
    
    #[derive(Parser)]
    #[command(name = "todo")]
    #[command(about = "A small task CLI")]
    struct Cli {
        #[command(subcommand)]
        command: Command,
    }
    
    #[derive(Subcommand)]
    enum Command {
        Add { task: String },
        Done { id: u32 },
    }
    
    fn main() {
        let cli = Cli::parse();
    
        match cli.command {
            Command::Add { task } => println!("Added task: {task}"),
            Command::Done { id } => println!("Marked task #{id} as done"),
        }
    }
    Common mistake

    Do not paste blindly

    AI tools are useful for crate setup, but they often mix old syntax with new versions. Ask for the crate version you use, then check the official docs and run cargo check.

    Also check that the generated code fits your project. If AI adds async, databases, or five extra crates for a simple CLI, remove the extra parts.

    Tip

    Keep updates controlled

    When something breaks after adding a crate, read the first compiler error before changing code. Rust errors usually point to the wrong type, missing import, or missing feature flag.

    Use cargo tree when your project feels heavy. It shows all dependency packages pulled in by your crates.

    cargo check
    cargo tree
    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    7. Build and submit the repo

    5 min

    In 30 seconds
    • Turn your Rust CLI into a repo an evaluator can run quickly.
    • Add README commands, Cargo setup, and proof of required Rust features.
    • Submit the public GitHub repo link on HackIndia with less back-and-forth.
    Key point

    One rule

    Your repo should answer one question fast: can someone clone it, run it, and see that you used Rust properly.

    Do not make the evaluator guess commands, inputs, or where your Result, match, and trait are used.

    Your to-do

    Final repo checklist

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    README skeleton

    Copy this shape and replace the project details. Keep it short. A busy reviewer should understand your CLI in one minute.

    # rupay-log
    
    A small Rust CLI that reads UPI expense lines and prints a category summary.
    
    ## What it does
    - Reads a text file of expenses
    - Parses amount, note, and category
    - Prints total spend by category
    
    ## Run
    ```bash
    cargo run -- sample.txt
    ```
    
    ## Sample input
    ```txt
    120 food canteen dosa
    40 travel metro
    299 study rust book
    ```
    
    ## Sample output
    ```txt
    food: ₹120
    travel: ₹40
    study: ₹299
    Total: ₹459
    ```
    
    ## Rust features used
    - Result: file reading and line parsing return Result
    - Pattern matching: CLI argument and parsed line handling use match
    - Trait: Summary trait formats category totals
    
    ## Check locally
    ```bash
    cargo fmt
    cargo clippy
    cargo test
    cargo run -- sample.txt
    ```
    Common mistake

    Common submission mistake

    Many builders submit code that works only on their laptop because the run command is missing or the sample file is not committed.

    If your CLI needs sample.txt, commit it. If it needs an argument, show the exact argument. If it needs an environment variable, avoid that for this track unless your README explains it clearly.

    Your to-do

    Prove the feature

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done

    Example

    Clean commit flow

    Use small commands you can repeat before submission. This is enough for most college project repos.

    git status
    cargo fmt
    cargo clippy
    cargo test
    cargo run -- sample.txt
    
    git add Cargo.toml Cargo.lock src README.md sample.txt
    git commit -m "Build Rust CLI submission"
    git branch -M main
    git remote add origin https://github.com/YOUR-USERNAME/YOUR-REPO.git
    git push -u origin main
    Tip

    Use AI carefully

    Ask AI to review your README like an evaluator: “Can you run this Rust CLI from a fresh clone? What command is missing?”

    Check its reply. AI may invent commands or files. Only keep suggestions that match your actual repo.

    Your to-do

    Do this now

    Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.

    0 of 5 done