Five Rust Projects Scoped for Learning, With Milestones and AI Guardrails
How to pick a Rust project that forces you to learn ownership, async and concurrency, with five scoped ideas, staged milestones and rules for using AI while learning.
This article is published in English.
Sources: 5 Rust Projects That Can Teach You More Than a Course
Rust front-loads a lot of concepts: ownership, borrowing, lifetimes, traits, error handling, concurrency, async, and an ecosystem of crates you have to navigate before anything useful runs. Many learners respond by collecting tutorials and still freeze in front of an empty main.rs. A more reliable path is to pick a project hard enough that each of those concepts becomes a problem you must solve, rather than a chapter you read. This guide describes five such projects, what each one will teach you, how to scope a first version you can finish, and how to use AI assistance so that it speeds you up without doing your learning for you.
Why a hard project beats another course
A course presents concepts in an order chosen by someone else, with the difficulty already removed. A real project reverses that. You hit a wall, and the wall tells you which concept you need next. Along the way you read documentation for real, make architectural choices you later regret, decode confusing compiler errors, notice performance problems and learn how separate crates fit together. Those are the skills that transfer to professional work, and they are hard to pick up passively.
The five projects below are chosen because each one pulls in several parts of the language at once. If you want a broader catalogue of ideas, our earlier piece on learning Rust through ten projects covers a wider range; here the focus is on scoping each project so it stays educational rather than overwhelming.
Project 1: a terminal coding agent of your own
Command-line AI coding assistants are one of the most active corners of developer tooling. Tools such as Claude Code, Codex and various open-source alternatives let a developer state a goal and have an agent inspect files, call tools, edit code and report back, instead of the developer issuing every command manually. A stripped-down version of that loop makes an excellent Rust exercise because it touches networking, processes, the file system and user interface all at once.
The core loop is small. The user types into a terminal interface, your program forwards the conversation to a language model, the model may reply with a request to use a tool, your Rust code executes that tool, and the result goes back to the model for the next step.
You → Terminal UI → LLM → Tool Request → Your Rust Program → Result → LLM
The interesting part is the arrow from the model back into your program. The model never touches your machine directly; it can only ask, and your code decides whether and how to act. That boundary is where most of the design work lives.
A first version you can finish
Start with a handful of tools and nothing else:
- open and read a file
- create or overwrite a file
- search within a directory
- execute a shell command
- report basic project information
- send each tool result back to the model
Once that loop works end to end, grow it in layers: keep conversation history across turns, stream responses as they arrive, add permission checks before destructive actions, describe tools with explicit schemas, support cancelling a running step, and improve how output is rendered in the terminal. Permission checks deserve particular care. An agent that can run arbitrary commands on your machine should ask before writing outside the project folder or executing anything it has not run before.
What it teaches
- async Rust and HTTP clients for talking to a model API
- JSON serialization and deserialization of requests, responses and tool calls
- handling streamed responses
- spawning and managing child processes
- file-system APIs
- modelling the agent loop as a state machine
- error handling across network, parsing and process failures
- command-line and terminal UI design
- tool calling as a protocol
A further advantage is that the project never really ends. You can keep improving it for months, which makes it a good long-term testbed. If you want to see how the same loop looks from a product and safety perspective, our article on building a personal AI agent with earned access discusses the trust side in more depth.
Decide what you are optimizing for
Before writing any code, be honest about the goal. Learning Rust, building a portfolio piece, shipping a tool you will use every day, experimenting with AI and preparing for a systems role all lead to different decisions. A portfolio piece rewards polish; a daily tool rewards reliability; a learning project rewards struggle.
If learning is the priority, resist letting an assistant generate the whole codebase. Removing the frustrating parts also removes much of the understanding those parts would have produced. Asking an assistant to explain a borrow checker error or clarify a confusing section of documentation is fine. Delegating the design before you understand the system is not. A project that takes three days to understand usually teaches far more than one generated in three hours.
Project 2: a desktop app with Tauri
Rust has a reputation for servers, command-line tools and low-level code, but it is also a capable choice for desktop software. Tauri pairs a web-based interface with a Rust backend, so you can build an application that looks and feels like modern desktop software while the logic that matters runs in Rust. If you are weighing Tauri against other options first, our comparison of JavaScript desktop frameworks lays out the trade-offs.
Skip the generic to-do list and build something you would actually open every day. One concrete idea is a local productivity assistant that records audio, transcribes speech to text, stores notes on your own disk and reads selected text back to you. Other good candidates include a personal file organizer, a Markdown workspace, a developer dashboard, a media utility or a local AI tool.
The idea matters less than the seam between the web interface and the native backend. Every button in the UI eventually becomes a call across that boundary, and designing that interface well is the real lesson. A sensible first milestone is one command exposed from Rust, invoked from the frontend, that does something the browser cannot do by itself, such as reading a folder listing.
What it teaches
- structuring a Rust application with more than one layer
- communication between the frontend and Rust
- packaging and distributing desktop applications
- local storage
- threads and background tasks that keep the UI responsive
- file-system operations
- calling external APIs and services
- state management
- error handling that surfaces useful messages in the UI
- security considerations for a native app that renders web content
The broader lesson is that software gets much harder once it meets a real operating system. Reading a file takes one line. Handling files safely, reporting errors clearly, staying responsive during slow work and behaving the same on different machines is an entirely different problem, and that gap is exactly the experience worth having.
Project 3: a tower-defense game in Bevy
A game is one of the few projects that can make you forget you are studying. It is also a demanding teacher, because many systems must run together every frame: entities, player input, physics, rendering, timing, transitions between states, and the performance budget that holds it all together.
Bevy is a strong fit for Rust learners because it is built on an Entity Component System (ECS). Instead of large objects that bundle every behaviour, an entity is little more than an identifier with components attached to it, and systems are functions that run over every entity with a given set of components. A projectile might have a position, a velocity and a damage value; a movement system updates everything with position and velocity, and a collision system handles anything that deals damage. This shift from objects to data plus functions is one of the more valuable mental models Rust and Bevy can give you.
Why tower defense in particular
The genre scales entity counts quickly, which is precisely what makes it educational. A single map can involve:
- hundreds of enemies at once
- multiple towers with different behaviours
- projectiles in flight
- health and damage systems
- pathfinding
- waves of attackers
- upgrades
- several enemy types
At that scale, correctness is no longer the only question. You start asking how much work the game does on every frame. That question leads naturally to profiling, data layout, system scheduling, memory usage and choosing algorithms that do not collapse as numbers grow. A good first milestone is a single enemy walking a fixed path and a single tower that shoots it; everything else can be layered on once that loop is stable.
What it teaches
- how an Entity Component System is structured
- the game loop and frame timing
- reacting to events
- managing state across menus, levels and game over
- performance profiling
- data-oriented design
- resource management
- rendering fundamentals
- input handling
- optimization
Games also teach something tutorials rarely manage: how several independent systems interact once the program becomes genuinely complicated, and how small changes in one system ripple into others.
Project 4: an embedded device with on-device ML
Constrained hardware is where Rust gets even more interesting: scarce memory, a small power budget and limited CPU time. Instead of targeting a laptop, target a microcontroller or a small edge device.
A practical example is a device that watches its environment and reacts to changes. It could sample:
- temperature
- humidity
- light levels
- motion
- sound
- other sensor readings
On top of that you can add a compact machine-learning model for one focused job: spotting anomalies, recognising gestures or listening for a wake word. Keep the model part modest; the real learning is in fitting everything onto constrained hardware.
You do not need expensive hardware to start. An emulator or simulator is enough to learn the architecture and the programming techniques, and you can move to a physical board once the software works.
Why embedded Rust feels different
A desktop program enjoys a full standard library, plenty of memory, a fast CPU and an operating system that handles countless low-level details. Embedded development takes most of that away, and with it comes a new set of topics:
no_stdRust, where the standard library is unavailable- working within tight memory limits
- hardware interfaces
- interrupts
- real-time behaviour
- device drivers
- communicating with peripherals
- cross-compilation
- the tooling and ecosystem around specific microcontrollers
This is one of the clearest demonstrations of why Rust matters beyond ordinary applications. You have to understand what the machine is really doing, because there is nothing underneath to hide it. Embedded work also connects Rust to robotics, automotive systems, industrial equipment and other hardware-heavy fields.
Project 5: an adaptive load balancer
For backend developers, another CRUD API rarely stretches your skills much. Building a piece of infrastructure does. A load balancer is a good candidate: it sits between clients and a pool of backend servers, and for each incoming request it decides which worker should handle it, rather than sending all traffic to one machine.
The basic shape is simple: clients connect to your Rust process, and it forwards each request to one of several workers.
┌─────────────┐
│ Clients │
└──────┬──────┘
│
▼
┌─────────────────┐
│ Rust Load │
│ Balancer │
└───────┬─────────┘
┌───┴───┐
▼ ▼
┌────────┐ ┌────────┐
│Worker 1│ │Worker 2│
└────────┘ └────────┘
Milestones from naive to adaptive
Begin with plain round-robin routing, where each request goes to the next worker in turn. It is easy to reason about and gives you a baseline. Then add intelligence in steps:
- measure response times and error rates per worker
- detect unhealthy workers and stop routing to them
- enforce connection limits
- try alternative routing strategies and compare them under load
Eventually the balancer can adjust its behaviour to live traffic conditions instead of following a fixed rule.
Where it gets difficult
Imagine one worker suddenly slows down. Should new requests keep going there? Almost certainly not. Harder still, picture a few workers degrading at once, each for its own reason: one is overloaded, one has a flaky network link, one is garbage-collecting. Your system has to observe what is happening and make decisions fast, without becoming the bottleneck itself. Working through that pulls in:
- concurrency
- networking fundamentals
- TCP and HTTP
- reverse proxying
- health checks and timeouts
- pooling connections to workers
- metrics collection
- spreading load fairly
- backpressure when every worker is busy
- handling failures gracefully
- distributed systems thinking
You could experiment with an AI-assisted routing policy, but treat it strictly as an experiment. Critical infrastructure should never depend blindly on a model's decisions, and a deterministic fallback must always be available. AI for its own sake adds nothing; the goal is to measure whether it actually improves the system compared with a simple, well-understood strategy.
What these projects have in common
The shared ingredient is not syntax. It is complexity that you cannot avoid.
A tutorial can show how Arc<Mutex<T>> works; a project makes you discover why shared state needs synchronization at all, usually after the compiler refuses to let you share it unsafely. Async functions are easy to explain on paper; a real application shows you what happens when several tasks interact, when a request is cancelled halfway, or when a network connection drops. A book can explain ownership; a project forces you to design structures where each resource has the correct owner and is released at the correct time. That gap is the difference between knowing a language and knowing how to build software with it.
Choose a project that is slightly uncomfortable
A useful selection rule: pick something just beyond your current ability. It should not be impossible, and it should not be something you already know how to build. The right project makes you think, repeatedly, that you have no idea yet how some part of it works.
That feeling is a good sign. Expect to read a lot of documentation, break your code, misread APIs, write an abstraction that turns out badly and later replace it, and argue with the compiler more than once. Then, a few days in, something that looked impossible will make sense. That experience is very hard to reproduce by watching one more tutorial.
Using AI without outsourcing the learning
How you use AI assistance can change how much a project teaches you more than any other factor. Modern assistants are genuinely useful: they clarify error messages, condense long documentation, propose approaches and help you dig into bugs. The line to watch is between using AI as a tool and using it as a substitute for understanding.
For a learning-focused project, a workable discipline looks like this:
- write the first version yourself
- read the relevant documentation before asking for help
- attempt each problem before requesting a solution
- when you are stuck, ask for an explanation rather than finished code
- implement the fix yourself, even if you now know what it should look like
- make sure you can explain every important part of the system without looking it up
None of this means avoiding AI. It means making sure you are still the one doing the thinking.
Matching a project to where you want to go
Let the kind of engineer you want to become guide the choice:
- AI and developer tooling: build the terminal coding agent
- Desktop software: build a Tauri application
- Games and performance: build the Bevy tower-defense game
- Robotics, hardware or low-level work: build the embedded device
- Backend infrastructure and distributed systems: the adaptive load balancer
Whichever you pick, do not aim for perfection. Get the smallest working version running, then iterate on it.
Key takeaways
- Choose projects whose difficulty forces you into ownership, concurrency, async and error handling, instead of reading about them in the abstract.
- Define a tiny first milestone for each project and grow it in layers; a working small loop teaches more than an ambitious design that never runs.
- Decide up front whether you are learning, building a portfolio piece or shipping a tool, because each goal justifies different shortcuts.
- Use AI to explain, not to author, while learning is the goal, and keep deterministic fallbacks wherever a model makes decisions in a real system.
- The cycle of building, breaking, investigating, redesigning and optimizing is where most Rust understanding comes from, and a weekend of it can outweigh many hours of passive tutorials.