Rust Whisperer: 12 Prompts That Turn LLMs Into Your Personal Borrow Checker, Lifetime Coach, and Async Mentor

Rust Whisperer: 12 Prompts That Turn LLMs Into Your Personal Borrow Checker, Lifetime Coach, and Async Mentor

Rust is notorious for its steep learning curve. The borrow checker, lifetimes, and async/await can feel like an impenetrable wall. But what if you could have a patient mentor available 24/7? Large Language Models (LLMs) like ChatGPT can serve as that mentor—if you know how to ask. This guide presents 12 battle-tested prompts that transform LLMs into a Rust expert, helping you master ownership, lifetimes, async, and more. Each prompt includes a real-world scenario, the exact prompt, and an example result. Whether you're coming from C++, Go, or are a complete beginner, these prompts will accelerate your Rust journey.

1. Decoding the Borrow Checker: From Error to Understanding

Task: You encounter a borrow checker error that you can't decipher. Instead of just getting a fix, you want to understand the underlying rule.

Prompt:

I'm getting this Rust error:

[PASTE ERROR]

Explain in simple terms why the borrow checker is complaining. Then, show me two ways to fix it: one that changes the code structure, and one that uses cloning (if applicable). Finally, give me a rule of thumb to avoid this in the future.

Example Result:
Input error:

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &v[0];
  |                  - immutable borrow occurs here
4 |     v.push(4);
  |     ^^^^^^^^^ mutable borrow occurs here
5 |     println!("The first element is: {}", first);
  |                                      ----- immutable borrow later used here

LLM response:
The error occurs because you have an immutable borrow (first) that is still alive when you try to mutably borrow v to push a new element. Rust's ownership rules allow either multiple immutable borrows or one mutable borrow, but not both simultaneously. To fix, either move the println! before the push, or clone the first element: let first = v[0];. Rule of thumb: avoid holding references while mutating the collection; use indices or clone when needed.

2. Lifetime Annotations Demystified

Task: You understand ownership but lifetimes still confuse you. You want a clear explanation with analogies.

Prompt:

Explain Rust lifetimes as if I'm a 10-year-old. Use an analogy with a library book. Then, show a function that returns a reference and requires lifetime annotations. Explain why the annotations are necessary.

Example Result:
LLM response:
Imagine you borrow a library book. The book must live at least as long as you keep the borrowing card. In Rust, a lifetime is like that borrowing period. If a function returns a reference, the compiler needs to know how long that reference is valid. For example:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

Here, 'a says the returned reference lives as long as the shorter of the two input references. This prevents returning a reference to data that might be dropped.

3. Generating Idiomatic Rust Code from Pseudocode

Task: You have an algorithm in mind but want to write it in idiomatic Rust, leveraging iterators and avoiding common pitfalls.

Prompt:

Convert this pseudocode into idiomatic Rust code:

function find_max_even(numbers):
    max = null
    for n in numbers:
        if n % 2 == 0 and (max is null or n > max):
            max = n
    return max

Use iterators and Option. Add comments explaining the idiomatic choices.

Example Result:

fn find_max_even(numbers: &[i32]) -> Option<i32> {
    numbers.iter()
.filter(

|&&n| n % 2 == 0)
        .max()
        .copied()
}

Comments: We use iter() to borrow, filter to keep evens, and max to find the maximum. copied converts Option<&i32> to Option<i32>. This is concise and avoids manual loops.

4. Async/Await and Tokio: Building a Concurrent Web Scraper

Task: You need to write an async function that fetches multiple URLs concurrently using Tokio and reqwest.

Prompt:

Write an async Rust program that fetches 5 URLs concurrently using Tokio and reqwest. Handle errors gracefully. Explain how async/await works in this context and why concurrency is beneficial.

Example Result:

use reqwest;
use tokio;

async fn fetch_url(url: &str) -> Result<String, reqwest::Error> {
    let resp = reqwest::get(url).await?;
    resp.text().await
}

#[tokio::main]
async fn main() {
    let urls = vec!["https://example.com", "https://rust-lang.org", "https://crates.io", "https://docs.rs", "https://tokio.rs"];
let futures = urls.iter().map(

|url| fetch_url(url));
    let results = futures::future::join_all(futures).await;
    for (url, result) in urls.iter().zip(results) {
        match result {
            Ok(text) => println!("{}: {} bytes", url, text.len()),
            Err(e) => eprintln!("{}: {}", url, e),
        }
    }
}

Explanation: Async allows non-blocking I/O. join_all runs all futures concurrently, reducing total time.

5. Writing Unit Tests for Edge Cases

Task: You have a function and want comprehensive unit tests covering edge cases.

Prompt:

For this Rust function:

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

Write unit tests covering: normal division, division by zero, negative numbers, and NaN inputs. Use #[cfg(test)] module.

Example Result:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_divide_normal() {
        assert_eq!(divide(10.0, 2.0), Ok(5.0));
    }

    #[test]
    fn test_divide_by_zero() {
        assert!(divide(10.0, 0.0).is_err());
    }

    #[test]
    fn test_divide_negative() {
        assert_eq!(divide(-10.0, 2.0), Ok(-5.0));
    }

    #[test]
    fn test_divide_nan() {
        let result = divide(f64::NAN, 1.0);
        assert!(result.unwrap().is_nan());
    }
}

6. Transitioning from C++ to Rust: Ownership vs. RAII

Task: You're a C++ developer struggling with Rust's ownership. You want a comparison.

Prompt:

I'm a C++ developer learning Rust. Explain ownership and borrowing by comparing to C++ RAII and smart pointers. Show a code example in both languages that demonstrates the difference.

Example Result:
LLM response: In C++, RAII ties resource lifetime to object lifetime, but you can have multiple pointers to the same resource. Rust enforces single ownership: only one variable owns a value at a time. Borrowing is like passing a reference, but checked at compile time. Example: C++ std::unique_ptr vs Rust Box. In Rust, moving a Box transfers ownership; in C++, you can copy a shared_ptr. This prevents data races and dangling pointers.

7. From Go to Rust: Concurrency Patterns

Task: You're a Go developer used to goroutines and channels. You want to implement similar patterns in Rust.

Prompt:

I'm coming from Go. Show me how to implement a worker pool in Rust using threads and channels, similar to Go's goroutines and channels. Compare the two approaches.

Example Result:
LLM response: In Go, you'd use go func() and chan. In Rust, use std::thread and std::sync::mpsc. Example:

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    for i in 0..5 {
        let tx = tx.clone();
        thread::spawn(move || {
            tx.send(i).unwrap();
        });
    }
    drop(tx);
    for received in rx {
        println!("Got: {}", received);
    }
}

Unlike Go, Rust's channels enforce ownership, preventing data races.

8. Debugging Async Deadlocks

Task: Your async code hangs, and you suspect a deadlock. You need help diagnosing it.

Prompt:

My Tokio app hangs. Here's the relevant code: [PASTE CODE]. How can I debug this? Explain common async deadlock causes and suggest fixes.

Example Result:
LLM response: Common causes: holding a mutex across await points, or blocking the executor. Use tokio::sync::Mutex instead of std::sync::Mutex in async code. Check for .await inside a loop holding a lock. Use tokio-console to inspect tasks. Fix: restructure to avoid holding locks across awaits.

9. Optimizing Performance with Profiling

Task: Your Rust code is slower than expected. You want to profile and optimize.

Prompt:

My Rust function is slow: [PASTE CODE]. Suggest profiling tools and optimization techniques. Provide a rewritten version if possible.

Example Result:
LLM response: Use perf or cargo flamegraph. Common optimizations: avoid unnecessary allocations, use iterators instead of loops, leverage &str over String. Rewritten example: replace Vec with slice, use iter().sum().

10. Writing Documentation and Examples

Task: You wrote a library and need to add doc comments and examples.

Prompt:

Add Rust doc comments to this function, including an example that can be run as a doc test:

fn add(a: i32, b: i32) -> i32 { a + b }

Example Result:

/// Adds two integers.
///
/// # Examples
///
/// ```
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
fn add(a: i32, b: i32) -> i32 { a + b }

11. Exploring Crates: Finding the Right Tool

Task: You need a crate for a specific task but don't know which one to choose.

Prompt:

I need to parse JSON in Rust. Compare serde_json, simd-json, and json. Which is best for high-performance? Provide a code example with serde_json.

Example Result:
LLM response: serde_json is the standard, well-maintained. simd-json is faster for large payloads but less mature. Example with serde_json:

use serde_json::Value;
let data = r#"{"name":"John"}"#;
let v: Value = serde_json::from_str(data).unwrap();
println!("{}", v["name"]);

12. Preparing for Rust Interviews

Task: You have a job interview for a Rust position and want to practice.

Prompt:

Generate 5 Rust interview questions on ownership, lifetimes, and async. Provide answers and explanations.

Example Result:
LLM response:
1. What is the difference between String and &str? ...
2. Explain lifetime elision rules. ...
3. How does async differ from threads? ...

These prompts are just the beginning. The key is to engage with the LLM iteratively: ask follow-ups, request clarifications, and test the code. Over time, you'll internalize Rust's concepts and write safer, faster code. Happy coding!

← All posts

Comments