Introduction
Rust has become a cornerstone for building reliable, performant software — from operating systems and embedded devices to command-line tools and WebAssembly modules. Its ownership model, zero-cost abstractions, and strong type system demand a specific mindset when crafting prompts for AI assistants or code generators. This article presents 15 carefully designed prompts across three critical domains: system programming, CLI development, and WebAssembly. Each prompt includes a concrete task, the exact prompt wording, and an example output to help you write safer, faster Rust code.
Whether you are a seasoned Rustacean or just starting, these prompts will guide you through common patterns, avoid pitfalls, and leverage Rust’s unique strengths. Let’s dive in.
System Programming Prompts
System programming in Rust often involves working with raw memory, operating system APIs, or concurrency. The following prompts help you write correct and efficient low-level code.
1. Safe FFI Wrapper
Task: Create a safe Rust wrapper around a C library function that accepts a callback.
Prompt:
"Write a safe Rust wrapper for a C function `void process_data(const uint8_t *data, size_t len, void (*callback)(int status))`. Use `std::ffi` and `unsafe` blocks responsibly, and ensure the callback is only called once."
Example Result:
use std::ffi::CStr;
use std::os::raw::c_void;
extern "C" {
fn process_data(data: *const u8, len: usize, callback: extern "C" fn(i32));
}
extern "C" fn callback_wrapper(status: i32) {
// Convert status and propagate safely
println!("Processing finished with status: {}", status);
}
pub fn safe_process_data(data: &[u8]) {
unsafe {
process_data(data.as_ptr(), data.len(), callback_wrapper);
}
}
This prompt emphasizes the extern ABI, raw pointer conversion, and how to encapsulate unsafe logic behind a safe function — a pattern recommended by the Rustonomicon.
2. Memory-Mapped File Reader
Task: Implement a zero-copy reader for a large binary file using memory mapping.
Prompt:
"Write a function that memory-maps a file and returns a `&[u8]` slice. Use `memmap2` crate. Ensure the file is not modified while mapped and handle errors with `Result`."
Example Result:
use memmap2::Mmap;
use std::fs::File;
use std::io::Result;
pub fn mmap_file(path: &str) -> Result<Mmap> {
let file = File::open(path)?;
unsafe { Mmap::map(&file) }
}
Memory mapping avoids copying data into user space, which is critical for high-performance parsers (e.g., database engines). The memmap2 documentation explains safety guarantees.
3. Lock-Free Hash Map with Crossbeam
Task: Build a concurrent hash map using crossbeam and atomic operations.
Prompt:
"Implement a simple lock-free hash map for `u64` keys using `crossbeam_epoch` for memory reclamation. Provide `insert` and `get` methods."
Example Result: (simplified)
use crossbeam_epoch::{self as epoch, Atomic, Owned};
use std::sync::atomic::Ordering;
struct Node {
key: u64,
value: u64,
next: Atomic<Node>,
}
pub struct HashMap {
buckets: Vec<Atomic<Node>>,
}
impl HashMap {
pub fn get(&self, key: u64) -> Option<u64> {
let guard = &epoch::pin();
let bucket_idx = (key as usize) % self.buckets.len();
let mut current = self.buckets[bucket_idx].load(Ordering::Acquire, guard);
while let Some(node) = unsafe { current.as_ref() } {
if node.key == key {
return Some(node.value);
}
current = node.next.load(Ordering::Acquire, guard);
}
None
}
}
Lock-free data structures are a key topic in systems programming; this prompt teaches hazard pointers via crossbeam_epoch.
4. Custom Allocator Wrapper
Task: Write a simple arena allocator that returns &mut [u8] and drops all allocations at once.
Prompt:
"Create a `BumpAllocator` that pre-allocates a fixed-size buffer and hands out slices without individual deallocation. Implement `Allocator` trait (requires nightly) or just a simple API."
Example:
pub struct BumpAllocator {
memory: Vec<u8>,
offset: usize,
}
impl BumpAllocator {
pub fn new(size: usize) -> Self {
Self { memory: vec![0; size], offset: 0 }
}
pub fn allocate(&mut self, size: usize) -> Option<&mut [u8]> {
if self.offset + size > self.memory.len() {
return None;
}
let slice = &mut self.memory[self.offset..self.offset + size];
self.offset += size;
Some(slice)
}
}
Arena allocators are common in game engines and interpreters; this prompt shows how to manage lifetimes manually.
5. Async I/O with Tokio and mio
Task: Create a minimal non-blocking TCP echo server using tokio and raw mio events.
Prompt:
"Write a Tokio-based TCP echo server that handles multiple connections concurrently. Use `tokio::net::TcpListener` and spawn a task per connection."
Example:
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = [0; 1024];
loop {
let n = socket.read(&mut buf).await.unwrap();
if n == 0 { break; }
socket.write_all(&buf[..n]).await.unwrap();
}
});
}
}
This is the foundation of many network services; the Tokio tutorial provides deeper context.
CLI Development Prompts
Rust’s CLI ecosystem is mature, with crates like clap, structopt, and serde. These prompts help build ergonomic command-line tools.
6. Subcommand Parser with clap
Task: Build a CLI tool with two subcommands (add and list) using clap derive API.
Prompt:
"Create a CLI with `add <task>` and `list` using clap v4. Use derive macros and handle the `--help` flag automatically."
Example:
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "todo")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Add { task: String },
List,
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Add { task } => println!("Added: {}", task),
Commands::List => println!("Listing tasks..."),
}
}
Clap’s derive API reduces boilerplate and follows the official guide.
7. JSON/YAML Configuration File
Task: Read a configuration file in JSON or YAML format using serde and serde_json/serde_yaml.
Prompt:
"Write a function that reads `config.json` or `config.yaml` into a `Config` struct. Support both formats based on file extension."
Example:
use serde::Deserialize;
use std::fs;
#[derive(Deserialize)]
struct Config {
host: String,
port: u16,
}
fn load_config(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
if path.ends_with(".json") {
Ok(serde_json::from_str(&content)?)
} else if path.ends_with(".yaml") || path.ends_with(".yml") {
Ok(serde_yaml::from_str(&content)?)
} else {
Err("Unsupported file format".into())
}
}
This pattern is ubiquitous in CLI tools; serde’s documentation covers advanced customization.
8. Progress Bar for Long Operations
Task: Show a progress bar while iterating over items. Use indicatif crate.
Prompt:
"Write a function that processes a list of files and displays a progress bar using `ProgressBar` from indicatif. Update the bar after each file."
Example:
use indicatif::ProgressBar;
fn process_files(files: &[String]) {
let pb = ProgressBar::new(files.len() as u64);
for file in files {
// process file...
std::thread::sleep(std::time::Duration::from_millis(100));
pb.inc(1);
}
pb.finish_with_message("Done");
}
Indicatif is a top choice for CLI UX; see its GitHub repo for more examples.
9. Error Handling with anyhow
Task: Use anyhow to simplify error propagation in a CLI tool.
Prompt:
"Rewrite a function that performs multiple fallible operations using `anyhow::Result` and the `bail!` macro for early exits."
Example:
use anyhow::{bail, Result};
fn read_and_parse(path: &str) -> Result<i32> {
let content = std::fs::read_to_string(path)?;
let num: i32 = content.trim().parse()?;
if num < 0 {
bail!("Negative numbers not allowed: {}", num);
}
Ok(num)
}
anyhow is standard in many CLI projects; the anyhow docs explain its benefits.
10. Interactive Selection (dialoguer)
Task: Present a list of options to the user and return the chosen one.
Prompt:
"Create an interactive prompt that asks the user to pick from a list of choices using `dialoguer::Select`. Print the selected item."
Example:
use dialoguer::Select;
fn main() {
let items = vec!["Item A", "Item B", "Item C"];
let selection = Select::new()
.with_prompt("Choose an option")
.items(&items)
.interact()
.unwrap();
println!("You selected: {}", items[selection]);
}
dialoguer powers many interactive CLIs; see its crate page.
WebAssembly Prompts
Rust compiles to WebAssembly through wasm-pack or wasm-bindgen. These prompts target browser and server-side WASM.
11. WASM Module with Memory Passing
Task: Export a Rust function that takes a byte slice and returns a processed string.
Prompt:
"Write a WASM function `greet(name: &str) -> String` using `wasm-bindgen`. Handle memory allocation automatically."
Example:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
wasm-bindgen handles the string pass-through; see the official book.
12. DOM Manipulation from Rust
Task: Create a button and add a click event listener using web-sys.
Prompt:
"Write a WASM function that creates a `<button>` element, sets its text, and attaches a click handler that changes the background color."
Example:
use wasm_bindgen::prelude::*;
use web_sys::{window, HtmlButtonElement, MouseEvent};
#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
let document = window().unwrap().document().unwrap();
let button: HtmlButtonElement = document.create_element("button")?.dyn_into()?;
button.set_inner_text("Click me");
let handle_click = Closure::wrap(Box::new(move
|_: MouseEvent| {
window()
.unwrap()
.document()
.unwrap()
.body()
.unwrap()
.style()
.set_property("background-color", "blue")
.unwrap();
}) as Box<dyn FnMut(MouseEvent)>);
button.add_event_listener_with_callback("click", handle_click.as_ref().unchecked_ref())?;
handle_click.forget(); // prevent leak
document.body().unwrap().append_child(&button)?;
Ok(())
}
This demonstrates interoperability with browser APIs through web-sys.
13. High-Performance Array Processing
Task: Write a Rust function that sums an array of f64 passed from JavaScript, returning the result.
Prompt:
"Export a function `sum_array(arr: &[f64]) -> f64` that computes the sum using SIMD-like loop unrolling. Use `wasm-bindgen`."
Example:
#[wasm_bindgen]
pub fn sum_array(arr: &[f64]) -> f64 {
arr.iter().sum()
}
For more efficiency, you can use std::simd (nightly) or packed_simd. The Rust WASM performance guide explains how to pass arrays.
14. Shared Memory with Web Workers
Task: Use SharedArrayBuffer and atomic operations from Rust to synchronize between worker threads.
Prompt:
"Write a WASM module that uses `std::sync::atomic` on a shared memory slice passed from JavaScript. Implement a simple counter increment."
Example:
use std::sync::atomic::{AtomicU32, Ordering};
#[wasm_bindgen]
pub fn increment_counter(ptr: *mut u32, len: usize) {
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, len) };
let atomic = AtomicU32::from_ptr(slice.as_mut_ptr());
atomic.store(42, Ordering::SeqCst);
}
This requires careful memory management; see MDN on SharedArrayBuffer and Rust’s atomic primitives.
15. Error Handling in WASM
Task: Return a Result that becomes a JavaScript promise on failure.
Prompt:
"Write a WASM function that performs a fallible operation and returns `Result<T, JsValue>`. Use `wasm_bindgen` to let JavaScript catch the error."
Example:
#[wasm_bindgen]
pub fn risky_operation(input: i32) -> Result<String, JsValue> {
if input < 0 {
Err(JsValue::from_str("Input must be non-negative"))
} else {
Ok(format!("Result: {}", input * 2))
}
}
Errors become JavaScript exceptions; the wasm-bindgen guide covers return types.
Conclusion
These 15 prompts span the three pillars of Rust’s ecosystem: systems programming, CLI tools, and WebAssembly. Each one targets a real-world task while reinforcing Rust’s core concepts — ownership, safety, zero-cost abstractions, and concurrency. By practicing these patterns, you can accelerate your development and write robust production code.
Remember to always consult the official documentation for the crates mentioned (clap, tokio, wasm-bindgen) and stay curious. Start by implementing one prompt today, and gradually build up your Rust toolkit.
Happy coding, and may your compilations always succeed!
Comments