The reviewer mindset
In Rust, “it compiles” means much more than in Java. The compiler removes whole classes of bugs, so your review can focus on design and on failure behavior.
What the compiler checks for you
Section titled “What the compiler checks for you”These problems do not get past cargo build. You do not need to look for them.
| Problem | In Java | In Rust |
|---|---|---|
| Use of a freed or moved value | Not possible (GC), but stale references are | Compile error (E0382) |
null dereference |
NullPointerException at run time |
No null. A missing value is Option, and you must handle None |
| Data race on shared memory | Possible; found by tests or in production | Compile error unless the data is behind a lock or an atomic |
| Iterator invalidation | ConcurrentModificationException at run time |
Compile error: you cannot change a Vec while you borrow it |
A forgotten switch case |
Compile error for switch expressions and pattern switch; an old switch statement can skip a case |
Every match must handle every case (E0004) |
| An ignored error | Checked exceptions force a catch or throws; unchecked ones do not |
Not handling a Result is a warning, and CI can make it an error |
Three examples. Each one is a real compiler error; this site’s CI proves the error code.
A match that forgets a case:
enum Status { Pending, Paid, Refunded }
fn label(status: &Status) -> &'static str { match status { Status::Pending => "pending", Status::Paid => "paid", // `Refunded` is missing: error E0004 }}An Option used as if it were the value:
fn find_price(sku: &str) -> Option<u32> { if sku == "A-1" { Some(250) } else { None }}
let price: u32 = find_price("A-1"); // error E0308: expected `u32`, found `Option<u32>`A thread that changes a list that the main thread also changes:
let mut audit_log = Vec::new();let worker = std::thread::spawn(|| { audit_log.push("order 17 shipped"); // error E0373: closure may outlive `audit_log`});audit_log.push("order 18 shipped");worker.join().unwrap();What the compiler does not check
Section titled “What the compiler does not check”The compiler proves that the code is memory-safe and free of data races. It does not prove that the code is right. These are your job:
- Logic. Wrong formula, wrong condition, off-by-one in business rules.
- Failure behavior.
unwrap(),expect(),panic!, andv[i]compile without a warning, and they stop the program when they fail. See S02:unwrap()/expect()in non-test code. - Error design. Errors as strings, errors that lose their cause, errors that the code ignores.
- Copies.
.clone()compiles and is often correct. In a loop or on a large collection it is a cost that Java never showed you. See S01:clone()to satisfy the borrow checker. - Design and API shape. Public fields, getters and setters for everything, Java class hierarchies copied into traits.
- Dependencies. A crate that does not exist, or one that is unmaintained.
- Performance. Allocation in hot loops, blocking calls in async code.
How AI agents write Rust
Section titled “How AI agents write Rust”Agents learned from a lot of Java, C#, and Python, and from Rust of every age. Two habits cause most of the problems that you will find:
- They translate Java patterns directly. Classes become structs with getters and setters. Inheritance becomes trait hierarchies. Object graphs become
Rc<RefCell<T>>everywhere (S03). Exceptions becomepanic!orResult<T, String>. - They fix compiler errors the fastest way, not the right way. The borrow checker says “use of moved value”; the agent adds
.clone()(S01). A lifetime error appears; the agent adds'static(S21). Clippy warns; the agent adds#[allow(...)](S25). The code now compiles, so the agent reports success.
The smell catalogue lists these patterns with an ID, a severity, and a ready-to-copy instruction for the agent. The review checklist groups them for a review session.