🎭 Rust System Interaction: Your Program Talks to the World!
The Big Picture: Your Program is Like a Little Person
Imagine your Rust program is a tiny person living inside your computer. This tiny person needs to:
- Listen to what you type (stdin)
- Talk back to you (stdout)
- Remember what you told it when you started it (command line arguments)
- Know secret notes left around the house (environment variables)
- Do chores like running other programs (std::process)
- Find its way around the house using addresses (PathBuf and Path)
Let’s meet each of these superpowers! 🦸
📥 stdin: The Ears of Your Program
What is stdin?
Think of stdin (standard input) like your program’s ears. When you type something on your keyboard, your program can hear it through stdin!
Simple Example: Asking for Your Name
use std::io;
fn main() {
println!("What's your name?");
let mut name = String::new();
io::stdin()
.read_line(&mut name)
.expect("Failed to read");
println!("Hello, {}!", name.trim());
}
What happens:
- Your program asks “What’s your name?”
- It opens its ears (stdin)
- You type “Alice” and press Enter
- It hears you and says “Hello, Alice!”
Real Life Analogy
It’s like a waiter at a restaurant:
- Waiter: “What would you like?”
- You: “Pizza please!”
- Waiter writes it down (stores in
name) - Waiter: “One pizza coming up!”
📤 stdout: The Voice of Your Program
What is stdout?
stdout (standard output) is your program’s voice. Every time you use println!, your program is speaking through stdout!
Simple Example: Counting to 3
fn main() {
println!("1... Getting ready!");
println!("2... Almost there!");
println!("3... GO!");
}
What happens: Your program “speaks” three lines to the screen.
stdout vs stderr: Two Voices!
Your program actually has two voices:
- stdout = Normal voice (for regular messages)
- stderr = Serious voice (for errors and warnings)
use std::io::{self, Write};
fn main() {
// Normal voice
println!("Everything is fine!");
// Serious voice for errors
eprintln!("Oops! Something went wrong!");
}
🎯 Command Line Arguments: Instructions at Startup
What are Command Line Arguments?
Imagine you’re sending a robot to the store. Before it leaves, you tell it:
- “Buy milk”
- “Get 2 bottles”
These instructions are like command line arguments—you give them when you start your program!
How to Run:
cargo run -- buy milk 2
Simple Example: Reading Arguments
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("You gave me {} instructions:", args.len());
for (i, arg) in args.iter().enumerate() {
println!(" {}: {}", i, arg);
}
}
Output:
You gave me 4 instructions:
0: target/debug/my_program
1: buy
2: milk
3: 2
Important!
args[0]is always the program’s nameargs[1],args[2], etc. are YOUR instructions
graph TD A["cargo run -- hello world"] --> B["args collected"] B --> C["args[0] = program name"] B --> D["args[1] = hello"] B --> E["args[2] = world"]
🔐 Environment Variables: Secret Notes in the Room
What are Environment Variables?
Think of environment variables like sticky notes hidden around your computer. Programs can read these notes to learn secret information!
Common notes include:
HOME= Where you live (your home folder)PATH= Where to find programsUSER= Your username
Reading Environment Variables
use std::env;
fn main() {
// Read a variable (might not exist)
match env::var("HOME") {
Ok(home) => println!("Your home: {}", home),
Err(_) => println!("HOME not set!"),
}
// Read with a default value
let user = env::var("USER")
.unwrap_or("stranger".to_string());
println!("Hello, {}!", user);
}
Setting Environment Variables
Your program can also leave its own sticky notes!
use std::env;
fn main() {
// Leave a note
env::set_var("MY_SECRET", "Rust is awesome");
// Read it back
let secret = env::var("MY_SECRET").unwrap();
println!("The secret: {}", secret);
}
Real Life Example
graph TD A["Computer starts"] --> B["Sets HOME=/Users/alice"] B --> C["Sets USER=alice"] C --> D["Your program runs"] D --> E["Reads HOME to find files"] D --> F["Reads USER for greeting"]
🚀 std::process: Running Other Programs
What is std::process?
Your Rust program can be a boss that tells other programs to do work! It’s like being able to call other helpers.
The Command Builder
use std::process::Command;
fn main() {
// Tell "echo" program to say hello
let output = Command::new("echo")
.arg("Hello from Rust!")
.output()
.expect("Failed to run command");
// Print what echo said
println!("{}",
String::from_utf8_lossy(&output.stdout));
}
Checking if it Worked
use std::process::Command;
fn main() {
let status = Command::new("ls")
.arg("-la")
.status()
.expect("Failed to run ls");
if status.success() {
println!("ls worked!");
} else {
println!("ls failed!");
}
}
Exiting Your Program
Sometimes you need to quit and tell the world if things went well or badly:
use std::process;
fn main() {
let age = 10;
if age < 18 {
println!("Too young!");
process::exit(1); // Exit with error code
}
println!("Welcome!");
// Normal exit (code 0) happens automatically
}
Exit Codes Explained
| Code | Meaning |
|---|---|
| 0 | Everything is fine! ✅ |
| 1 | Something went wrong ❌ |
| Other | Custom error codes |
🗺️ PathBuf and Path: Finding Your Way
The Difference: Path vs PathBuf
Think of file paths like addresses:
- Path = A printed address on paper (you can read it, but not change it)
- PathBuf = An address in a notebook (you can read AND edit it)
graph TD A["File Paths"] --> B["Path"] A --> C["PathBuf"] B --> D["Read-only address<br>Like &str"] C --> E["Editable address<br>Like String"]
Creating Paths
use std::path::{Path, PathBuf};
fn main() {
// PathBuf - you own it, can change it
let mut my_path = PathBuf::from("/home/alice");
my_path.push("documents");
my_path.push("notes.txt");
println!("Full path: {:?}", my_path);
// Output: "/home/alice/documents/notes.txt"
// Path - borrowed, read-only
let borrowed: &Path = my_path.as_path();
println!("Borrowed: {:?}", borrowed);
}
Useful Path Methods
use std::path::Path;
fn main() {
let path = Path::new("/home/alice/photo.png");
// Get the file name
println!("Name: {:?}", path.file_name());
// Output: Some("photo.png")
// Get the extension
println!("Ext: {:?}", path.extension());
// Output: Some("png")
// Get the parent folder
println!("Parent: {:?}", path.parent());
// Output: Some("/home/alice")
// Check if it exists
println!("Exists: {}", path.exists());
}
Building Paths Safely
Don’t glue paths together with strings! Use join:
use std::path::PathBuf;
fn main() {
let base = PathBuf::from("/home/alice");
// ✅ GOOD: Use join
let good = base.join("documents").join("file.txt");
// ❌ BAD: String concatenation
// let bad = "/home/alice" + "/documents";
println!("Safe path: {:?}", good);
}
Why join is Better
- Works on Windows (
\) AND Linux (/) - Handles edge cases (double slashes, etc.)
- Type-safe!
🎨 Putting It All Together
Here’s a mini program that uses EVERYTHING we learned:
use std::env;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::Command;
fn main() {
// 1. Get command line arguments
let args: Vec<String> = env::args().collect();
// 2. Read environment variable
let home = env::var("HOME").unwrap_or_default();
// 3. Use stdout
println!("Welcome! Home is: {}", home);
// 4. Use stdin
print!("What file to create? ");
io::stdout().flush().unwrap();
let mut filename = String::new();
io::stdin().read_line(&mut filename).unwrap();
// 5. Use PathBuf
let mut path = PathBuf::from(&home);
path.push(filename.trim());
// 6. Use process to run a command
let status = Command::new("touch")
.arg(&path)
.status();
match status {
Ok(s) if s.success() => {
println!("Created: {:?}", path);
}
_ => eprintln!("Failed to create file!"),
}
}
🏆 You Did It!
You now know how Rust programs:
- Listen with stdin 👂
- Speak with stdout 🗣️
- Remember startup instructions with args 📋
- Read secret notes with environment variables 🔐
- Boss other programs with std::process 👔
- Navigate with Path and PathBuf 🗺️
Your Rust programs are no longer isolated—they can talk to the whole system! 🎉
