A prioritized plan for building out the rush shell, ordered from easiest to hardest.
Each step teaches new Rust concepts while making the shell more realistic and functional.
- Builtins:
exit,echo,type,pwd,cd(with tilde expansion) - External commands: PATH lookup and execution via
Command::new - Argument splitting:
split_whitespace(no quote handling) - Cross-platform: Unix/Windows executable detection and path handling
- Dependencies:
anyhow(present but unused)
Difficulty: ⭐ | Impact: High
Replace split_whitespace with a hand-written tokenizer that handles:
- Single quotes (
'hello world'→ one token) - Double quotes (
"hello world"→ one token) - Backslash escapes (
\",\\,\)
Why: echo "hello world" currently breaks into "hello and world". This is the most immediate usability fix.
Rust concepts: char iterators, match expressions, Vec<String> building, unit testing with #[cfg(test)].
Difficulty: ⭐⭐ | Impact: Medium (code quality)
- Replace the big
match command { ... }block with aBuiltinenum and atrait Executable. - Move each builtin into its own file under
src/builtins/(cd.rs,echo.rs, etc.). - Start using
anyhow::Resultwith?instead ofunwrap().
Why: The single main.rs will become unmanageable as features grow. This restructures the project early.
Rust concepts: enums, traits, impl blocks, module system (mod / use), Result / ? error handling.
Difficulty: ⭐⭐ | Impact: High
- Parse redirect operators out of the token list.
- Open files with
std::fs::File/OpenOptions. - Wire them into
Command::new().stdin()/.stdout()/.stderr(). - Add a
Redirectstruct to hold the fd, mode, and path.
Rust concepts: File ownership (the Stdio::from call takes ownership), OpenOptions builder, enums for redirect mode (Overwrite / Append).
Difficulty: ⭐⭐⭐ | Impact: High
- Split input on unquoted
|. - Spawn each command with
Stdio::piped(). - Chain each child's stdout to the next child's stdin.
- Wait on all children; propagate the last exit code.
Rust concepts: ownership transfer of ChildStdout → Stdio, Vec<Child>, .windows(2) iteration, process lifecycle management.
Difficulty: ⭐⭐⭐ | Impact: Medium
- Expand
$VARand${VAR}in tokens before execution. - Add
export KEY=VALUEbuiltin (sets in localHashMapand viaenv::set_var). - Add
unsetbuiltin. - Introduce a
struct Shellto hold all shell state (env, builtins, etc.).
Rust concepts: HashMap, Entry API, &str vs String borrowing, structuring program state in a struct.
Difficulty: ⭐⭐⭐⭐ | Impact: Medium
- Use the
signal-hookcrate so Ctrl-C kills the foreground child, not the shell. - Add
&suffix for background execution. - Add
jobs,fg,bgbuiltins, tracking children in aVec.
Rust concepts: Arc<AtomicBool>, cross-thread signaling, unsafe awareness (signal handlers), crate integration, process groups.
Difficulty: ⭐⭐⭐⭐ | Impact: High (UX)
- Replace raw
stdin().read_linewith therustylinecrate. - Get arrow-key navigation, history search (Ctrl-R), persistent
~/.rush_history. - Implement the
Helpertrait for tab-completion of builtins, file paths, and PATH commands.
Rust concepts: trait implementation (Completer, Hinter, Highlighter, Validator), generics, lifetimes in trait impls.
Difficulty: ⭐⭐⭐⭐⭐ | Impact: High
- Accept
rush script.shviastd::env::args(), read the file, execute line-by-line. - Build a simple AST:
enum Statement { Simple(Command), If { cond: Box<Statement>, then: Vec<Statement>, else_: Option<Vec<Statement>> }, For { var: String, list: Vec<String>, body: Vec<Statement> }, }
- Write a recursive
evalfunction. Use exit codes as booleans.
Rust concepts: recursive enums with Box<T>, pattern matching on nested structures, Iterator for file lines, tree-walking interpretation.
| Concern | Notes |
|---|---|
| Shell state struct | Steps 2–8 all benefit from a struct Shell { builtins, env, jobs, history, ... }. Introduce it in Step 2. |
| Testing | The tokenizer (Step 1) and variable expansion (Step 5) are pure functions — ideal for #[test] modules. Use the assert_cmd crate for integration tests. |
anyhow usage |
Already a dependency but unused. Every step is a chance to replace unwrap() with ?. |
| Error messages | Follow the Unix convention: command: context: error message. |
Last updated: 2025-02-26