Building a Robust Rust Expert AI with Rig and Gemini
Published 2026-05-03 04:12:54 · 224 views
In this guide, we will build a command-line tool that acts as a Rust Programming Language Expert. We will use the Rig framework to interface with Googleโs Gemini 2.5 Flash Lite model.
Our goal is to move beyond a simple "hello world" script and build a modular, production-ready CLI with proper error handling and environment management.
๐ Prerequisites
Before we dive into the code, ensure you have:
Rust installed (via
).rustup.rs A Gemini API Key from the
.Google AI Studio
๐ Project Setup
First, create a new Rust project and navigate into the directory:
cargo new rust-expert-ai
cd rust-expert-ai
The .env File
Create a file named .env in your project root. This keeps your sensitive API keys out of your source code.
# Replace with your actual Gemini API Key
GEMINI_API_KEY=your_api_key_here
๐ฆ The Crates (Dependencies)
We are using four key crates to make this project work:
rig-core: A high-level library for building LLM-powered applications. It provides the "Agent" abstraction.tokio: The industry-standard asynchronous runtime for Rust. Necessary because AI requests are network-bound.dotenvy: A modern fork ofdotenvused to load variables from our.envfile.anyhow: Makes error handling significantly easier by providing a flexible error type.
Add them to your Cargo.toml:
cargo add rig-core tokio --features full
cargo add dotenvy anyhow
๐ป The Implementation
Weโve structured the code into modular functions to ensure readability and maintainability.
1. The Entry Point and Initialization
Our main function acts as the orchestrator. It handles the environment setup before handing control over to the chat loop.
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Load .env and fetch API key
dotenvy::dotenv().ok();
let api_key = std::env::var("GEMINI_API_KEY")
.expect("GEMINI_API_KEY must be set");
// Initialize the Gemini Client
let client = rig::providers::gemini::Client::new(&api_key);
// Build the specialized Agent
let agent = build_expert_agent(&client);
println!("๐ Rust Expert AI is online!");
run_chat_loop(agent).await?;
Ok(())
}
2. Defining the Agent's "Personality"
By separating the agent configuration, we can easily tweak the model or the "Preamble" (the instructions given to the AI).
fn build_expert_agent(client: &rig::providers::gemini::Client) -> rig::agent::Agent<rig::providers::gemini::CompletionModel> {
client
.agent("gemini-2.5-flash-lite")
.preamble("You are a Rust Programming Language Expert. Provide idiomatic, safe, and performant Rust advice.")
.build()
}
3. The Interactive I/O Loop
This function handles the "Human-in-the-loop" logic, including flushing the terminal and handling exit commands.
async fn run_chat_loop(agent: rig::agent::Agent<rig::providers::gemini::CompletionModel>) -> anyhow::Result<()> {
use std::io::{self, Write};
use rig::completion::Prompt;
loop {
print!("\n> Ask Rust Expert: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
if input.eq_ignore_ascii_case("exit") || input.eq_ignore_ascii_case("quit") {
break;
}
if input.is_empty() { continue; }
println!("Thinking...");
match agent.prompt(input).await {
Ok(res) => println!("\nResponse: {}", res),
Err(e) => eprintln!("Error: {}", e),
}
}
Ok(())
}
๐ Running the Project
To start your AI assistant, simply run:
cargo run
You can now ask questions like:
"What is the difference between String and &str?"
"Explain the Borrow Checker using a library analogy."
"How do I implement a thread-safe singleton?"
๐ก Summary of Best Practices Used
Asynchronous I/O: Leveraging
tokioto keep the application responsive during API calls.Environment Safety: Using
dotenvyto prevent leaking API keys.Conciseness: Using
anyhowto reduce boilerplatematchstatements for errors.Separation of Concerns: Modularizing the agent creation and the UI loop makes the code testable.
Happy coding, and may your borrow checker always be happy!