Skip to content

Latest commit

 

History

History
381 lines (290 loc) · 9.12 KB

File metadata and controls

381 lines (290 loc) · 9.12 KB

Getting Started with Rust - Premier League CLI

This guide assumes you've never written Rust before. We'll go step-by-step!

Prerequisites

Install Rust

  1. Open your terminal
  2. Run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  3. Follow the prompts (just press Enter to accept defaults)
  4. Close and reopen your terminal
  5. Verify installation: rustc --version and cargo --version

Get Your API Key

  1. Go to https://www.football-data.org/client/register
  2. Register for a free account
  3. Copy your API token - you'll need it later

Step 1: Initialize the Project

What is Cargo?

Cargo is Rust's package manager (like npm for Node.js). It handles:

  • Creating new projects
  • Managing dependencies
  • Building and running your code
  • Running tests

Commands to Run

# Navigate to your project folder (you're already here!)
cd /Users/adamoldin/Documents/Sidogrejer/pl-table-cli

# Initialize a new Rust project
cargo init

# See what was created
ls -la

What Just Happened?

  • Cargo.toml - Your project's configuration file (like package.json)
  • src/main.rs - Your main entry point
  • .gitignore - Already configured for Rust

Try It Out

# Run the default "Hello, world!" program
cargo run

You should see:

   Compiling pl-table-cli v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.50s
     Running `target/debug/pl-table-cli`
Hello, world!

Step 2: Understand the Basics

Look at src/main.rs

fn main() {
    println!("Hello, world!");
}

Explanation:

  • fn main() - Every Rust program starts here (like main() in C or Java)
  • println! - A macro (note the !) that prints to console
  • ; - Statements end with semicolons

Rust Basics You'll Need

Variables

let name = "Arsenal";           // Immutable (can't change)
let mut score = 0;              // Mutable (can change)
score += 3;                     // Now score is 3

Data Types

let position: u32 = 1;          // Unsigned 32-bit integer
let points: i32 = 52;           // Signed 32-bit integer
let name: String = String::from("Arsenal");
let ratio: f64 = 2.5;           // 64-bit float

Strings (Two Types!)

let s1 = "Arsenal";             // &str - string slice (borrowed, immutable)
let s2 = String::from("Arsenal"); // String - owned, can grow/shrink

Step 3: Add Dependencies

Edit Cargo.toml

Open Cargo.toml and replace the [dependencies] section with:

[dependencies]
clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.11", features = ["json", "blocking"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
comfy-table = "7.1"
anyhow = "1.0"
dotenvy = "0.15"

What Are These?

  • clap - Parses command-line arguments (e.g., --team Arsenal)
  • reqwest - Makes HTTP requests to the API
  • serde - Converts JSON to Rust structs and vice versa
  • serde_json - Works with serde for JSON specifically
  • comfy-table - Creates pretty tables in the terminal
  • anyhow - Makes error handling easier
  • dotenvy - Reads .env files for secrets

Download Dependencies

cargo build

This will download all dependencies (might take a minute the first time).


Step 4: Set Up Environment Variables

Create .env file

touch .env

Add this line (replace YOUR_API_KEY with your actual key from football-data.org):

FOOTBALL_DATA_API_KEY=your_actual_api_key_here

Create .env.example template

touch .env.example

Add this line:

FOOTBALL_DATA_API_KEY=your_api_key_here

Update .gitignore

Make sure .env is in your .gitignore so you don't commit your API key:

echo ".env" >> .gitignore

Step 5: Understand Rust Modules

What Are Modules?

Modules organize code into separate files/folders. Think of them like folders for your code.

Module Structure We'll Create

src/
├── main.rs         # Entry point - coordinates everything
├── cli.rs          # Defines command-line arguments
├── api/            # Folder for API-related code
│   ├── mod.rs      # Declares this folder as a module
│   ├── client.rs   # HTTP client code
│   └── models.rs   # Data structures
└── display/        # Folder for display-related code
    ├── mod.rs      # Declares this folder as a module
    └── table.rs    # Table formatting code

How to Declare Modules in main.rs

mod cli;           // Tells Rust to look for src/cli.rs
mod api;           // Tells Rust to look for src/api/mod.rs
mod display;       // Tells Rust to look for src/display/mod.rs

Step 6: Key Rust Concepts (Before We Code)

1. Ownership

Every value in Rust has ONE owner. When the owner goes out of scope, the value is dropped.

{
    let s = String::from("hello");   // s owns this string
}   // s goes out of scope, string is freed automatically

2. Borrowing

You can "borrow" a value without taking ownership:

fn print_length(s: &String) {  // &String = borrowed reference
    println!("Length: {}", s.len());
}  // s is returned to the caller

let my_string = String::from("hello");
print_length(&my_string);  // Borrow it
println!("{}", my_string); // Still works! We still own it

3. Result Type (Error Handling)

Functions that can fail return Result<T, E>:

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

// Using it:
match divide(10, 2) {
    Ok(result) => println!("Result: {}", result),
    Err(e) => println!("Error: {}", e),
}

// Or with ? operator (propagates errors):
let result = divide(10, 2)?;  // Returns early if Err

4. Option Type (Nullable Values)

Instead of null, Rust uses Option<T>:

let some_number: Option<i32> = Some(5);
let no_number: Option<i32> = None;

match some_number {
    Some(n) => println!("Got: {}", n),
    None => println!("Got nothing"),
}

5. Structs (Data Structures)

struct Team {
    name: String,
    points: u32,
    position: u32,
}

let arsenal = Team {
    name: String::from("Arsenal"),
    points: 52,
    position: 1,
};

println!("{} has {} points", arsenal.name, arsenal.points);

6. Derive Macros (Automatic Implementations)

#[derive(Debug, Clone)]  // Auto-implements Debug and Clone traits
struct Team {
    name: String,
}

let team = Team { name: String::from("Arsenal") };
println!("{:?}", team);  // Debug print: Team { name: "Arsenal" }

Step 7: Build It Step by Step

Here's the order I recommend (ask me for help with each step!):

Phase 1: Basic Structure

  1. Create module files - Set up the folder structure
  2. Define CLI arguments - What commands will your app accept?
  3. Test CLI - Make sure --help works

Phase 2: API Integration

  1. Define data models - Create structs for the API response
  2. Create API client - Function to fetch data
  3. Test API call - Just print the raw JSON first

Phase 3: Display

  1. Parse JSON - Convert API response to your structs
  2. Basic table - Display without colors first
  3. Add colors - Make it pretty!

Phase 4: Features

  1. Team filter - Implement --team flag
  2. Form display - Implement --form flag
  3. Error handling - Make errors user-friendly

Useful Cargo Commands

# Check if your code compiles (fast, doesn't produce binary)
cargo check

# Compile your project
cargo build

# Compile with optimizations (slower build, faster runtime)
cargo build --release

# Build and run
cargo run

# Run with arguments
cargo run -- --help
cargo run -- --team Arsenal

# Format your code
cargo fmt

# Check for common mistakes
cargo clippy

# Run tests
cargo test

# Clean build artifacts
cargo clean

Getting Help

In This Project

  • Ask me questions! I'm here to explain concepts and help you debug
  • Tell me which step you're on, and I'll break it down further

General Rust Resources

Common Questions

Q: What's the difference between String and &str? A: String is owned/mutable, &str is a borrowed view into a string. Use String when you need to own/modify, &str for reading.

Q: What does & mean? A: It's a reference (borrow). You're borrowing the value without taking ownership.

Q: What does ? do? A: It's the "try" operator. If the Result is Err, it returns early from the function. If it's Ok, it unwraps the value.

Q: Why do I need mut? A: Variables are immutable by default in Rust. You need mut to make them mutable.

Q: What's a trait? A: Like an interface - it defines behavior that types can implement.


Ready to Start?

Tell me when you're ready to begin, and which step you want to tackle first! I'll give you:

  1. Detailed instructions for that step
  2. Code examples
  3. Explanations of what each part does
  4. Common mistakes to avoid

Let's build this together!