--- name: convert-java-rust description: Convert Java code to idiomatic Rust. Use when migrating Java projects to Rust, translating Java patterns to idiomatic Rust, or refactoring Java codebases. Extends meta-convert-dev with Java-to-Rust specific patterns. --- # Convert Java to Rust Convert Java code to idiomatic Rust. This skill extends `meta-convert-dev` with Java-to-Rust specific type mappings, idiom translations, and tooling. ## This Skill Extends - `meta-convert-dev` - Foundational conversion patterns (APTV workflow, testing strategies) For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first. ## This Skill Adds - **Type mappings**: Java types → Rust types - **Idiom translations**: Java patterns → idiomatic Rust - **Error handling**: Java exceptions → Rust Result - **Concurrency**: Java threads/ExecutorService → Rust async/await - **Memory/Ownership**: Garbage collection → ownership/borrowing - **OOP patterns**: Java classes/inheritance → Rust structs/traits - **Null safety**: null references → Option - **Metaprogramming**: Java annotations/reflection → Rust macros/traits ## This Skill Does NOT Cover - General conversion methodology - see `meta-convert-dev` - Java language fundamentals - see `lang-java-dev` - Rust language fundamentals - see `lang-rust-dev` - Reverse conversion (Rust → Java) - see `convert-rust-java` --- ## Quick Reference | Java | Rust | Notes | |----------|----------|-------| | `String` | `String` / `&str` | Owned vs borrowed | | `int` | `i32` | 32-bit signed integer | | `long` | `i64` | 64-bit signed integer | | `float` | `f32` | 32-bit float | | `double` | `f64` | 64-bit float | | `boolean` | `bool` | Direct mapping | | `List` | `Vec` | Growable array | | `Map` | `HashMap` | Hash table | | `Set` | `HashSet` | Unique collection | | `Optional` | `Option` | Nullable values | | `null` | `None` in `Option` | Explicit nullability | | `throws Exception` | `Result` | Type-safe errors | | `interface` | `trait` | Behavioral contracts | | `class` | `struct` + `impl` | Data + behavior | | `@Override` | No annotation needed | Traits enforce signature | | `synchronized` | `Mutex` / `RwLock` | Explicit locking | | `Thread` | `std::thread` / `tokio::task` | OS threads / async tasks | ## When Converting Code 1. **Analyze source thoroughly** before writing target 2. **Map types first** - create type equivalence table 3. **Preserve semantics** over syntax similarity 4. **Adopt target idioms** - don't write "Java code in Rust syntax" 5. **Handle edge cases** - null checks, error paths, resource cleanup 6. **Test equivalence** - same inputs → same outputs --- ## Type System Mapping ### Primitive Types | Java | Rust | Notes | |----------|----------|-------| | `boolean` | `bool` | Direct mapping | | `byte` | `i8` | 8-bit signed integer | | `short` | `i16` | 16-bit signed integer | | `int` | `i32` | 32-bit signed integer (most common) | | `long` | `i64` | 64-bit signed integer | | `float` | `f32` | 32-bit floating point | | `double` | `f64` | 64-bit floating point (most common) | | `char` | `char` | Unicode scalar value (4 bytes in Rust) | | `void` | `()` | Unit type | **Note:** Java `char` is 16-bit UTF-16, Rust `char` is 32-bit Unicode scalar. ### Boxed Primitives | Java | Rust | Notes | |----------|----------|-------| | `Integer` | `i32` | Primitives don't need boxing in Rust | | `Long` | `i64` | No autoboxing/unboxing | | `Double` | `f64` | Direct primitive usage | | `Boolean` | `bool` | No wrapper types needed | | `Character` | `char` | Direct usage | ### String Types | Java | Rust | Notes | |----------|----------|-------| | `String` | `String` | Owned, heap-allocated UTF-8 | | `String` (param) | `&str` | Borrowed string slice for parameters | | `StringBuilder` | `String` | Use `String` with `push_str`, `push` | | `char[]` | `Vec` | Character array | | `byte[]` | `Vec` | Byte array | ### Collection Types | Java | Rust | Notes | |----------|----------|-------| | `ArrayList` | `Vec` | Growable array | | `LinkedList` | `std::collections::LinkedList` | Doubly-linked list (rarely used) | | `HashMap` | `HashMap` | Hash table, K must be Hash + Eq | | `TreeMap` | `BTreeMap` | Ordered map, K must be Ord | | `HashSet` | `HashSet` | Unique collection | | `TreeSet` | `BTreeSet` | Ordered unique collection | | `ArrayDeque` | `VecDeque` | Double-ended queue | | `PriorityQueue` | `BinaryHeap` | Max-heap by default | | `T[]` | `Vec` | Dynamic array | | `T[]` (fixed) | `[T; N]` | Fixed-size array | ### Nullable Types | Java | Rust | Notes | |----------|----------|-------| | `@Nullable T` | `Option` | Explicit nullability | | `@NonNull T` | `T` | Non-null by default in Rust | | `Optional` | `Option` | Direct mapping | | `null` | `None` | Null variant | ### Error Types | Java | Rust | Notes | |----------|----------|-------| | `throws Exception` | `Result` | Type-safe error handling | | `try/catch` | `match result` or `?` | Pattern matching or propagation | | `Throwable` | `Error` trait | Error interface | | `RuntimeException` | `panic!` / `Result` | Unrecoverable vs recoverable | ### Composite Types | Java | Rust | Notes | |----------|----------|-------| | `class Foo { }` | `struct Foo { }` + `impl Foo { }` | Data + behavior separation | | `interface Bar { }` | `trait Bar { }` | Behavioral contract | | `enum Status { }` | `enum Status { }` | Algebraic data types in Rust | | `record Point(int x, int y)` | `struct Point { x: i32, y: i32 }` | Immutable by default in Rust | | `Pair` | `(K, V)` | Tuple | ### Generic Types | Java | Rust | Notes | |----------|----------|-------| | `` | `` | Type parameter | | `` | `` | Bounded type parameter | | `` | No direct equivalent | Use trait objects | | `` | `_` (type inference) | Wildcard | | `List` | `Vec` | Bounded wildcard | | `Class` | `PhantomData` | Type token | --- ## Idiom Translation ### Pattern 1: Null Checking **Java:** ```java public String getUserName(User user) { if (user == null) { return "Anonymous"; } if (user.getName() == null || user.getName().isEmpty()) { return "Anonymous"; } return user.getName(); } ``` **Rust:** ```rust fn get_user_name(user: Option<&User>) -> &str { user.and_then(|u| { if u.name.is_empty() { None } else { Some(u.name.as_str()) } }) .unwrap_or("Anonymous") } // Or more idiomatically with pattern matching: fn get_user_name(user: Option<&User>) -> &str { match user { Some(u) if !u.name.is_empty() => &u.name, _ => "Anonymous", } } ``` **Why this translation:** - `Option` makes nullability explicit in the type system - No null pointer exceptions possible at runtime - Combinators like `and_then` and `unwrap_or` are idiomatic - Pattern matching with guards is more expressive - Borrowed references avoid unnecessary cloning ### Pattern 2: Exception Handling **Java:** ```java public Config readConfig(String path) throws IOException { String content = Files.readString(Path.of(path)); return parseConfig(content); } public void processConfig(String path) { try { Config config = readConfig(path); apply(config); } catch (IOException e) { System.err.println("Failed to read config: " + e.getMessage()); } } ``` **Rust:** ```rust use std::fs; use std::path::Path; fn read_config(path: &Path) -> Result { let content = fs::read_to_string(path)?; parse_config(&content) } fn process_config(path: &Path) { match read_config(path) { Ok(config) => apply(config), Err(e) => eprintln!("Failed to read config: {}", e), } } // Or with the ? operator in a Result-returning function: fn process_config(path: &Path) -> Result<(), std::io::Error> { let config = read_config(path)?; apply(config); Ok(()) } ``` **Why this translation:** - `Result` encodes success/failure in the type system - The `?` operator propagates errors ergonomically (like Java `throws`) - Pattern matching makes error handling explicit - No hidden control flow (exceptions jumping up the stack) - Errors are values, not exceptional control flow ### Pattern 3: Optional Chaining **Java:** ```java public String getCityName(User user) { return Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCity) .map(City::getName) .orElse("Unknown"); } ``` **Rust:** ```rust fn get_city_name(user: Option<&User>) -> &str { user.and_then(|u| u.address.as_ref()) .and_then(|a| a.city.as_ref()) .map(|c| c.name.as_str()) .unwrap_or("Unknown") } // Or with pattern matching: fn get_city_name(user: Option<&User>) -> &str { match user { Some(User { address: Some(Address { city: Some(City { name, .. }), .. }), .. }) => name, _ => "Unknown", } } ``` **Why this translation:** - Direct mapping from Java `Optional` to Rust `Option` - Rust's `Option` methods are similar to Java's - Pattern matching can destructure nested `Option`s - Borrowed references avoid cloning ### Pattern 4: Stream/Iterator Operations **Java:** ```java List names = users.stream() .filter(user -> user.getAge() > 18) .map(User::getName) .map(String::toUpperCase) .collect(Collectors.toList()); int totalAge = users.stream() .mapToInt(User::getAge) .sum(); ``` **Rust:** ```rust let names: Vec = users .iter() .filter(|user| user.age > 18) .map(|user| user.name.to_uppercase()) .collect(); let total_age: i32 = users .iter() .map(|user| user.age) .sum(); ``` **Why this translation:** - Rust iterators are zero-cost abstractions (like Java streams) - Similar combinator API: `filter`, `map`, `collect`, `sum` - Rust iterators are lazy (like Java streams) - No need for specialized primitive streams (`mapToInt`, etc.) - More explicit borrowing with `iter()` vs `into_iter()` ### Pattern 5: Builder Pattern **Java:** ```java public class Request { private final String url; private final String method; private final Map headers; private Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers; } public static class Builder { private String url; private String method = "GET"; private Map headers = new HashMap<>(); public Builder url(String url) { this.url = url; return this; } public Builder method(String method) { this.method = method; return this; } public Builder header(String key, String value) { this.headers.put(key, value); return this; } public Request build() { return new Request(this); } } } // Usage Request request = new Request.Builder() .url("https://api.example.com") .method("POST") .header("Content-Type", "application/json") .build(); ``` **Rust:** ```rust use std::collections::HashMap; struct Request { url: String, method: String, headers: HashMap, } struct RequestBuilder { url: String, method: String, headers: HashMap, } impl RequestBuilder { fn new(url: impl Into) -> Self { Self { url: url.into(), method: String::from("GET"), headers: HashMap::new(), } } fn method(mut self, method: impl Into) -> Self { self.method = method.into(); self } fn header(mut self, key: impl Into, value: impl Into) -> Self { self.headers.insert(key.into(), value.into()); self } fn build(self) -> Request { Request { url: self.url, method: self.method, headers: self.headers, } } } // Usage let request = RequestBuilder::new("https://api.example.com") .method("POST") .header("Content-Type", "application/json") .build(); ``` **Why this translation:** - Similar builder pattern structure - Rust uses `self` consumption for method chaining - No need for nested `Builder` class (separate struct) - `impl Into` accepts both `String` and `&str` - More ergonomic with fewer allocations ### Pattern 6: Interface Implementation **Java:** ```java interface Reader { int read(byte[] buffer) throws IOException; } class FileReader implements Reader { private String path; @Override public int read(byte[] buffer) throws IOException { // Implementation return buffer.length; } } void processReader(Reader reader) throws IOException { byte[] buffer = new byte[1024]; int bytesRead = reader.read(buffer); // Process buffer } ``` **Rust:** ```rust use std::io; trait Reader { fn read(&mut self, buffer: &mut [u8]) -> io::Result; } struct FileReader { path: String, } impl Reader for FileReader { fn read(&mut self, buffer: &mut [u8]) -> io::Result { // Implementation Ok(buffer.len()) } } fn process_reader(reader: &mut R) -> io::Result<()> { let mut buffer = vec![0u8; 1024]; let bytes_read = reader.read(&mut buffer)?; // Process buffer Ok(()) } ``` **Why this translation:** - Rust traits are explicitly implemented with `impl Trait for Type` - Generic functions use trait bounds (``) - Mutable borrows (`&mut`) make mutation explicit - The `?` operator replaces `throws` declarations - No `@Override` annotation needed (enforced by trait) ### Pattern 7: Inheritance vs Composition **Java:** ```java abstract class Animal { private String name; public Animal(String name) { this.name = name; } public abstract void makeSound(); public void sleep() { System.out.println(name + " is sleeping"); } } class Dog extends Animal { public Dog(String name) { super(name); } @Override public void makeSound() { System.out.println("Woof!"); } } ``` **Rust:** ```rust // Use traits instead of abstract classes trait Animal { fn name(&self) -> &str; fn make_sound(&self); // Default implementation (like concrete methods in abstract class) fn sleep(&self) { println!("{} is sleeping", self.name()); } } struct Dog { name: String, } impl Animal for Dog { fn name(&self) -> &str { &self.name } fn make_sound(&self) { println!("Woof!"); } } // Alternative: Composition with delegation struct AnimalData { name: String, } struct Dog { data: AnimalData, } impl Dog { fn make_sound(&self) { println!("Woof!"); } fn sleep(&self) { println!("{} is sleeping", self.data.name); } } ``` **Why this translation:** - Rust favors composition over inheritance - Traits define shared behavior without state - No virtual method dispatch overhead by default - More flexible than rigid class hierarchies - Prefer trait bounds over inheritance for polymorphism ### Pattern 8: Static Methods and Factory Patterns **Java:** ```java class User { private String name; private int age; private User(String name, int age) { this.name = name; this.age = age; } public static User create(String name, int age) { if (age < 0) { throw new IllegalArgumentException("Age cannot be negative"); } return new User(name, age); } public static User createAnonymous() { return new User("Anonymous", 0); } } ``` **Rust:** ```rust struct User { name: String, age: u32, } impl User { // Associated function (like static method) fn new(name: impl Into, age: u32) -> Self { Self { name: name.into(), age, } } // Factory method with validation fn create(name: impl Into, age: i32) -> Result { if age < 0 { return Err("Age cannot be negative"); } Ok(Self { name: name.into(), age: age as u32, }) } // Named constructor fn anonymous() -> Self { Self { name: String::from("Anonymous"), age: 0, } } } // Usage let user = User::new("Alice", 30); let user2 = User::create("Bob", 25)?; let anon = User::anonymous(); ``` **Why this translation:** - Rust uses associated functions instead of static methods - No `static` keyword needed (no `self` parameter) - Factory methods return `Result` for validation - Named constructors are idiomatic (`new`, `with_capacity`, etc.) - Private constructors not needed (use `pub` selectively) --- ## Paradigm Translation: OOP → Systems Programming ### Mental Model Shift: Object-Oriented → Ownership-Based | Java Concept | Rust Approach | Key Insight | |------------------|-------------------|-------------| | Class with state | `struct` + `impl` blocks | Data and behavior separated but associated | | Inheritance | Composition + traits | Favor composition over deep hierarchies | | Polymorphism (subtyping) | Trait objects (`dyn Trait`) or generics | Static dispatch (generics) vs dynamic (trait objects) | | Encapsulation | Module visibility + `pub` | Privacy at module level, not class level | | Constructor | Associated function `new()` | No special constructor syntax | | Garbage collection | Ownership + borrowing | Compiler-enforced memory safety | | Null references | `Option` | Null safety in type system | | Exceptions | `Result` | Errors as values | ### Memory Management Mental Model | Java Model | Rust Model | Conceptual Translation | |----------------|----------------|------------------------| | Heap allocation automatic | Explicit (`Box`, `Vec`, `String`) | Ownership makes allocation visible | | GC reclaims memory | Automatic via RAII (`Drop`) | Deterministic cleanup at scope end | | References everywhere | Borrows (`&`, `&mut`) | Explicit lifetime tracking | | No manual cleanup | No manual cleanup | Same safety, different mechanism | | Shared mutable state | `Mutex`, `RefCell`, interior mutability | Mutation rules enforced by compiler | ### Concurrency Mental Model | Java Model | Rust Model | Conceptual Translation | |----------------|----------------|------------------------| | `synchronized` blocks | `Mutex` / `RwLock` | Lock protects data, not code | | Thread-safe by convention | Thread-safe by type system (`Send`, `Sync`) | Compiler prevents data races | | `ExecutorService` | `tokio::Runtime` / `async-std` | Async runtime for task scheduling | | `Future` (Java 8+) | `Future` trait + `async`/`await` | First-class async support | | Heavyweight threads | OS threads or lightweight async tasks | Choose cost based on use case | --- ## Error Handling ### Java Exception Model → Rust Result Model Java uses exceptions for both expected and unexpected errors. Rust distinguishes between recoverable errors (`Result`) and unrecoverable errors (`panic!`). **Mapping:** | Java | Rust | Use Case | |------|------|----------| | Checked exceptions | `Result` | Recoverable errors (expected) | | Unchecked exceptions | `Result` or `panic!` | Recoverable or programmer errors | | `throws` clause | Return type `Result` | Signature shows fallibility | | `try/catch` | `match result` or `if let Err` | Explicit error handling | | `try/catch` (propagate) | `?` operator | Early return on error | | `finally` | RAII / `Drop` trait | Automatic cleanup | | `throw new Exception()` | `Err(...)` or `panic!()` | Return error or abort | ### Pattern: Multiple Exception Types **Java:** ```java public Data processFile(String path) throws IOException, ParseException { String content = Files.readString(Path.of(path)); return parseData(content); } try { Data data = processFile("config.json"); } catch (IOException e) { System.err.println("IO error: " + e.getMessage()); } catch (ParseException e) { System.err.println("Parse error: " + e.getMessage()); } ``` **Rust:** ```rust use std::fs; use std::path::Path; // Define error enum to combine multiple error types #[derive(Debug)] enum ProcessError { Io(std::io::Error), Parse(String), } impl From for ProcessError { fn from(e: std::io::Error) -> Self { ProcessError::Io(e) } } fn process_file(path: &Path) -> Result { let content = fs::read_to_string(path)?; // Auto-converts via From parse_data(&content).map_err(ProcessError::Parse) } match process_file(Path::new("config.json")) { Ok(data) => { /* use data */ }, Err(ProcessError::Io(e)) => eprintln!("IO error: {}", e), Err(ProcessError::Parse(e)) => eprintln!("Parse error: {}", e), } // Or use anyhow/error-stack for simplified error handling: use anyhow::Result; fn process_file(path: &Path) -> Result { let content = fs::read_to_string(path)?; let data = parse_data(&content)?; Ok(data) } ``` **Why this translation:** - Custom error enums replace multiple exception types - `From` trait enables automatic conversion with `?` - Pattern matching handles different error cases - `anyhow` crate provides ergonomic error handling for applications - `thiserror` crate simplifies custom error types ### Pattern: Try-with-Resources **Java:** ```java try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) { String line = reader.readLine(); // Process line } catch (IOException e) { System.err.println("Error: " + e.getMessage()); } ``` **Rust:** ```rust use std::fs::File; use std::io::{self, BufRead, BufReader}; use std::path::Path; fn read_first_line(path: &Path) -> io::Result { let file = File::open(path)?; let reader = BufReader::new(file); let mut line = String::new(); reader.lines().next() .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "Empty file"))? } // File is automatically closed when it goes out of scope (Drop trait) match read_first_line(Path::new("file.txt")) { Ok(line) => println!("{}", line), Err(e) => eprintln!("Error: {}", e), } ``` **Why this translation:** - Rust's RAII (Drop trait) automatically cleans up resources - No need for explicit try-with-resources syntax - Scope-based cleanup is deterministic - More type-safe than runtime resource management --- ## Concurrency Patterns ### Java Concurrency → Rust Concurrency Rust provides both traditional OS threads and lightweight async/await concurrency. ### Pattern 1: Basic Threading **Java:** ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } Counter counter = new Counter(); List threads = new ArrayList<>(); for (int i = 0; i < 10; i++) { Thread t = new Thread(() -> { for (int j = 0; j < 1000; j++) { counter.increment(); } }); threads.add(t); t.start(); } for (Thread t : threads) { t.join(); } System.out.println("Count: " + counter.getCount()); ``` **Rust:** ```rust use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { for _ in 0..1000 { let mut num = counter.lock().unwrap(); *num += 1; } }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Count: {}", *counter.lock().unwrap()); ``` **Why this translation:** - `Arc>` combines reference counting (Arc) with mutual exclusion (Mutex) - Mutex protects the data, not the code block - Type system prevents data races at compile time - Explicit cloning makes shared ownership visible - `move` closure captures ownership ### Pattern 2: ExecutorService → Tokio Async **Java:** ```java ExecutorService executor = Executors.newFixedThreadPool(4); List> futures = new ArrayList<>(); for (String url : urls) { Future future = executor.submit(() -> fetchUrl(url)); futures.add(future); } List results = new ArrayList<>(); for (Future future : futures) { try { results.add(future.get()); } catch (InterruptedException | ExecutionException e) { System.err.println("Error: " + e.getMessage()); } } executor.shutdown(); ``` **Rust:** ```rust use tokio; #[tokio::main] async fn main() { let urls = vec!["url1", "url2", "url3"]; let tasks: Vec<_> = urls .into_iter() .map(|url| tokio::spawn(async move { fetch_url(url).await })) .collect(); let mut results = Vec::new(); for task in tasks { match task.await { Ok(result) => results.push(result), Err(e) => eprintln!("Error: {}", e), } } } async fn fetch_url(url: &str) -> String { // Async HTTP request String::from(url) } ``` **Why this translation:** - Tokio provides async runtime (like ExecutorService) - `async`/`await` syntax is more ergonomic than futures - Tasks are lightweight (like virtual threads in Java 21) - No need for explicit thread pool management - Type-safe async with `Future` trait ### Pattern 3: CompletableFuture → Async/Await **Java:** ```java CompletableFuture future1 = CompletableFuture.supplyAsync(() -> fetchData()); CompletableFuture future2 = future1.thenApply(data -> parseData(data)); CompletableFuture future3 = future2.thenAccept(value -> processValue(value)); future3.exceptionally(ex -> { System.err.println("Error: " + ex.getMessage()); return null; }); future3.join(); ``` **Rust:** ```rust use tokio; #[tokio::main] async fn main() { match fetch_and_process().await { Ok(()) => println!("Success"), Err(e) => eprintln!("Error: {}", e), } } async fn fetch_and_process() -> Result<(), Box> { let data = fetch_data().await?; let value = parse_data(&data).await?; process_value(value).await?; Ok(()) } async fn fetch_data() -> Result { // Async operation Ok(String::from("data")) } async fn parse_data(data: &str) -> Result { data.parse() } async fn process_value(value: i32) -> Result<(), std::io::Error> { println!("Value: {}", value); Ok(()) } ``` **Why this translation:** - `async`/`await` is more readable than chaining futures - `?` operator propagates errors through async chain - No need for explicit `exceptionally` handlers - Type-safe error handling with `Result` - Composable async functions --- ## Memory & Ownership ### Java GC → Rust Ownership The biggest paradigm shift from Java to Rust is memory management. Java uses garbage collection; Rust uses compile-time ownership tracking. ### Core Ownership Rules 1. **Each value has a single owner** (unlike Java where references are shared freely) 2. **When the owner goes out of scope, the value is dropped** (like Java finalization, but deterministic) 3. **Values can be borrowed immutably or mutably** (unlike Java where everything is mutable unless final) ### Pattern 1: Ownership Transfer **Java:** ```java // Java freely shares references List list1 = new ArrayList<>(); list1.add("hello"); List list2 = list1; // Both point to same list list2.add("world"); System.out.println(list1.size()); // 2 ``` **Rust:** ```rust // Rust transfers ownership by default let mut list1 = vec![String::from("hello")]; let list2 = list1; // Ownership transferred to list2 // println!("{:?}", list1); // Compile error: list1 moved list2.push(String::from("world")); println!("{}", list2.len()); // 2 // To share, use borrowing: let mut list1 = vec![String::from("hello")]; let list2 = &list1; // Borrow immutably println!("{:?}", list1); // OK: list1 still owns the data println!("{:?}", list2); // OK: borrowing ``` **Why this matters:** - Rust prevents use-after-move bugs at compile time - No runtime overhead (no reference counting) - Clear ownership semantics ### Pattern 2: Cloning vs Borrowing **Java:** ```java void processData(List data) { // Can mutate the list data.add("new item"); } List myData = new ArrayList<>(); processData(myData); // myData is modified ``` **Rust:** ```rust // Option 1: Borrow mutably fn process_data(data: &mut Vec) { data.push(String::from("new item")); } let mut my_data = vec![]; process_data(&mut my_data); // my_data is modified // Option 2: Borrow immutably (cannot modify) fn read_data(data: &Vec) { for item in data { println!("{}", item); } // data.push(...); // Compile error: cannot mutate } read_data(&my_data); // Option 3: Take ownership (consumes the value) fn consume_data(data: Vec) { // data is moved here, caller loses access } // my_data is gone after this consume_data(my_data); // println!("{:?}", my_data); // Compile error ``` **Why this translation:** - Explicit borrowing prevents accidental mutation - Ownership transfer is visible in function signatures - Compiler enforces no data races or aliasing bugs ### Pattern 3: Reference Counting (Rc/Arc) **Java:** ```java // Java automatically manages shared references class Node { int value; List children; } Node parent = new Node(); Node child1 = new Node(); Node child2 = new Node(); parent.children.add(child1); parent.children.add(child2); // All nodes share references, GC cleans up when unreachable ``` **Rust:** ```rust use std::rc::Rc; struct Node { value: i32, children: Vec>, } let child1 = Rc::new(Node { value: 1, children: vec![], }); let child2 = Rc::new(Node { value: 2, children: vec![], }); let parent = Node { value: 0, children: vec![Rc::clone(&child1), Rc::clone(&child2)], }; // Rc provides shared ownership with reference counting // Arc for thread-safe reference counting ``` **Why this translation:** - `Rc` (single-threaded) or `Arc` (thread-safe) for shared ownership - Explicit cloning makes reference counting visible - No cycles by default (use `Weak` for weak references) - More predictable than GC --- ## Metaprogramming ### Java Annotations/Reflection → Rust Macros/Traits Java uses runtime reflection and annotations. Rust uses compile-time macros and traits. ### Pattern 1: Annotations → Derive Macros **Java:** ```java @Data // Lombok annotation @Entity @Table(name = "users") public class User { @Id @GeneratedValue private Long id; @Column(nullable = false) private String name; @Column(unique = true) private String email; } ``` **Rust:** ```rust use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct User { pub id: Option, pub name: String, pub email: String, } // Or with a custom derive macro for ORM: #[derive(Debug, Entity)] #[table(name = "users")] pub struct User { #[id] #[generated] pub id: Option, #[column(nullable = false)] pub name: String, #[column(unique = true)] pub email: String, } ``` **Why this translation:** - Derive macros generate code at compile time (no runtime reflection) - Type-safe (errors caught during compilation) - Zero runtime overhead - `serde` is the standard serialization framework ### Pattern 2: Reflection → Trait Objects **Java:** ```java void processObject(Object obj) { if (obj instanceof String) { String s = (String) obj; System.out.println("String: " + s); } else if (obj instanceof Integer) { Integer i = (Integer) obj; System.out.println("Integer: " + i); } } // Or with reflection: Class clazz = obj.getClass(); Method method = clazz.getMethod("toString"); Object result = method.invoke(obj); ``` **Rust:** ```rust // Prefer enums over runtime type checking enum Value { String(String), Integer(i32), } fn process_value(value: Value) { match value { Value::String(s) => println!("String: {}", s), Value::Integer(i) => println!("Integer: {}", i), } } // Or use trait objects for polymorphism: trait Printable { fn print(&self); } impl Printable for String { fn print(&self) { println!("String: {}", self); } } impl Printable for i32 { fn print(&self) { println!("Integer: {}", self); } } fn process_printable(obj: &dyn Printable) { obj.print(); } ``` **Why this translation:** - Rust avoids runtime reflection (unsafe and slow) - Enums are type-safe alternatives to instanceof - Trait objects (`dyn Trait`) for runtime polymorphism - Most metaprogramming done at compile time with macros ### Pattern 3: Custom Annotations → Attribute Macros **Java:** ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Cached { int ttl() default 60; } public class Service { @Cached(ttl = 300) public Data fetchData(String key) { // Method implementation } } // Runtime processing with reflection for (Method method : Service.class.getDeclaredMethods()) { if (method.isAnnotationPresent(Cached.class)) { Cached cached = method.getAnnotation(Cached.class); int ttl = cached.ttl(); // Setup caching } } ``` **Rust:** ```rust // Define attribute macro (in a proc-macro crate) #[proc_macro_attribute] pub fn cached(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse ttl from attr // Generate wrapper code that caches results // Return modified function } // Usage #[cached(ttl = 300)] pub fn fetch_data(key: &str) -> Data { // Method implementation } // Macro expands at compile time to: pub fn fetch_data(key: &str) -> Data { // Check cache // If miss, call original function and cache result } ``` **Why this translation:** - Rust macros run at compile time - No runtime reflection overhead - Type-safe macro expansion - More powerful than annotations (can generate arbitrary code) --- ## Serialization ### Jackson → Serde Java uses Jackson for JSON serialization. Rust uses Serde, which is more flexible and type-safe. ### Pattern: JSON Serialization **Java:** ```java import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.*; @JsonIgnoreProperties(ignoreUnknown = true) public class Config { @JsonProperty("api_key") private String apiKey; private String endpoint; @JsonIgnore private String internalState; @JsonProperty(access = JsonProperty.Access.READ_ONLY) private LocalDateTime createdAt; // Getters and setters } ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(config); Config parsed = mapper.readValue(json, Config.class); ``` **Rust:** ```rust use serde::{Serialize, Deserialize}; use chrono::{DateTime, Utc}; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] struct Config { #[serde(rename = "api_key")] api_key: String, endpoint: String, #[serde(skip)] internal_state: String, #[serde(skip_serializing)] created_at: DateTime, } // Serialization let json = serde_json::to_string(&config)?; let pretty_json = serde_json::to_string_pretty(&config)?; // Deserialization let parsed: Config = serde_json::from_str(&json)?; ``` **Why this translation:** - Serde is compile-time type-safe - Zero runtime overhead - More flexible than Jackson (works with JSON, YAML, TOML, MessagePack, etc.) - Errors caught at compile time, not runtime --- ## Build and Dependencies ### Maven/Gradle → Cargo Java uses Maven or Gradle. Rust uses Cargo, which is simpler and faster. ### Pattern: Dependency Management **Java (Maven):** ```xml com.fasterxml.jackson.core jackson-databind 2.15.0 org.junit.jupiter junit-jupiter 5.10.0 test ``` **Java (Gradle):** ```kotlin dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0") testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") } ``` **Rust (Cargo.toml):** ```toml [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1", features = ["full"] } [dev-dependencies] criterion = "0.5" ``` **Why this translation:** - Cargo is simpler (one file vs XML/Groovy) - Built-in features system - Faster dependency resolution - Lock file (Cargo.lock) ensures reproducible builds ### Common Commands | Maven/Gradle | Cargo | Purpose | |--------------|-------|---------| | `mvn compile` / `gradle build` | `cargo build` | Compile | | `mvn test` / `gradle test` | `cargo test` | Run tests | | `mvn package` / `gradle jar` | `cargo build --release` | Build release | | `mvn install` / `gradle publishToMavenLocal` | `cargo install` | Install binary | | `mvn clean` / `gradle clean` | `cargo clean` | Clean build | | `mvn dependency:tree` / `gradle dependencies` | `cargo tree` | Show deps | --- ## Testing ### JUnit → Cargo Test Java uses JUnit for testing. Rust has built-in testing support. ### Pattern: Unit Tests **Java:** ```java import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; class CalculatorTest { private Calculator calculator; @BeforeEach void setUp() { calculator = new Calculator(); } @Test void shouldAddTwoNumbers() { int result = calculator.add(2, 3); assertEquals(5, result); } @Test void shouldThrowOnDivisionByZero() { assertThrows(ArithmeticException.class, () -> { calculator.divide(10, 0); }); } } ``` **Rust:** ```rust struct Calculator; impl Calculator { fn add(&self, a: i32, b: i32) -> i32 { a + b } fn divide(&self, a: i32, b: i32) -> Result { if b == 0 { Err("Division by zero") } else { Ok(a / b) } } } #[cfg(test)] mod tests { use super::*; #[test] fn should_add_two_numbers() { let calculator = Calculator; let result = calculator.add(2, 3); assert_eq!(result, 5); } #[test] fn should_return_error_on_division_by_zero() { let calculator = Calculator; let result = calculator.divide(10, 0); assert!(result.is_err()); } } ``` **Why this translation:** - Built-in test framework (no external dependency) - Tests live next to code in `#[cfg(test)]` modules - Assertions are macros: `assert!`, `assert_eq!`, `assert_ne!` - No need for setup/teardown (use RAII pattern) --- ## Common Pitfalls ### Pitfall 1: Assuming Null Everywhere **Problem:** In Java, any reference can be null. In Rust, values are non-null by default. **Java:** ```java String name = getName(); // Might be null int length = name.length(); // NullPointerException! ``` **Rust:** ```rust // Wrong: trying to use null let name = get_name(); // Returns Option // let length = name.len(); // Compile error: Option has no len() // Right: handle Option explicitly let name = get_name(); let length = name.map(|s| s.len()).unwrap_or(0); // Or with pattern matching: match get_name() { Some(name) => println!("Length: {}", name.len()), None => println!("No name"), } ``` ### Pitfall 2: Mutating Shared References **Problem:** In Java, shared references can be mutated freely. Rust enforces exclusive mutation. **Java:** ```java List list = new ArrayList<>(); List ref1 = list; List ref2 = list; ref1.add("hello"); // OK ref2.add("world"); // OK ``` **Rust:** ```rust // Wrong: multiple mutable references let mut list = vec![]; let ref1 = &mut list; let ref2 = &mut list; // Compile error: cannot borrow as mutable more than once ref1.push("hello"); ref2.push("world"); // Right: use immutable borrows or take ownership let mut list = vec![]; list.push("hello"); list.push("world"); // Or use interior mutability (RefCell, Mutex) use std::cell::RefCell; let list = RefCell::new(vec![]); list.borrow_mut().push("hello"); list.borrow_mut().push("world"); ``` ### Pitfall 3: Expecting Inheritance **Problem:** Java relies heavily on class inheritance. Rust favors composition and traits. **Java:** ```java class Animal { } class Dog extends Animal { } Animal animal = new Dog(); // Polymorphism via inheritance ``` **Rust:** ```rust // Wrong: trying to use inheritance // Rust has no inheritance! // Right: use traits trait Animal { fn make_sound(&self); } struct Dog; impl Animal for Dog { fn make_sound(&self) { println!("Woof!"); } } fn process_animal(animal: &dyn Animal) { animal.make_sound(); } let dog = Dog; process_animal(&dog); ``` ### Pitfall 4: Checked Exceptions vs Result **Problem:** Java uses checked exceptions that must be declared. Rust uses `Result` as a return type. **Java:** ```java // Java: throws in signature public Data readFile(String path) throws IOException { // ... } ``` **Rust:** ```rust // Wrong: trying to throw exceptions // Rust has no exceptions! // Right: return Result fn read_file(path: &Path) -> Result { // ... } // Or use the ? operator to propagate fn process() -> Result<(), std::io::Error> { let data = read_file(Path::new("file.txt"))?; Ok(()) } ``` ### Pitfall 5: String Confusion **Problem:** Java has one `String` type. Rust has `String` (owned) and `&str` (borrowed). **Java:** ```java String s1 = "hello"; String s2 = new String("world"); void process(String s) { } ``` **Rust:** ```rust // Wrong: using only String fn process(s: String) { } // Takes ownership! let s1 = String::from("hello"); process(s1); // println!("{}", s1); // Compile error: s1 was moved // Right: use &str for parameters fn process(s: &str) { } let s1 = String::from("hello"); process(&s1); // Borrow println!("{}", s1); // OK: still own s1 // String literals are &str let s2 = "world"; // Type: &str ``` ### Pitfall 6: Integer Overflow **Problem:** Java silently wraps on integer overflow. Rust panics in debug mode. **Java:** ```java int max = Integer.MAX_VALUE; int overflow = max + 1; // Wraps to Integer.MIN_VALUE ``` **Rust:** ```rust // Debug mode: panics on overflow let max: i32 = i32::MAX; // let overflow = max + 1; // Panic in debug, wraps in release // Right: use checked/wrapping/saturating arithmetic let overflow = max.checked_add(1); // Returns None let wrapping = max.wrapping_add(1); // Always wraps let saturating = max.saturating_add(1); // Clamps to max ``` ### Pitfall 7: Cloning Performance **Problem:** Java clones collections implicitly. Rust makes cloning explicit and visible. **Java:** ```java List list1 = Arrays.asList("a", "b", "c"); List list2 = new ArrayList<>(list1); // Clone ``` **Rust:** ```rust // Explicit cloning let list1 = vec!["a", "b", "c"]; let list2 = list1.clone(); // Explicit, visible // Prefer borrowing when possible let list1 = vec!["a", "b", "c"]; process_list(&list1); // Borrow, no clone println!("{:?}", list1); // Still available ``` --- ## Tooling | Java Tool | Rust Equivalent | Purpose | |-----------|----------------|---------| | Maven / Gradle | Cargo | Build system, dependency management | | JUnit | Built-in `#[test]` | Unit testing | | Mockito | `mockall` | Mocking | | Javadoc | `cargo doc` (rustdoc) | Documentation generation | | IntelliJ IDEA | VS Code + rust-analyzer | IDE | | Eclipse | RustRover (JetBrains) | IDE | | Checkstyle / PMD | `cargo clippy` | Linting | | Google Java Format | `cargo fmt` (rustfmt) | Code formatting | | JaCoCo | `cargo-tarpaulin` / `cargo-llvm-cov` | Code coverage | | VisualVM | `perf` / `valgrind` / `flamegraph` | Profiling | --- ## Examples ### Example 1: Simple - HTTP Client **Before (Java):** ```java import java.net.http.*; import java.net.URI; public class HttpExample { public static void main(String[] args) throws Exception { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.example.com/data")) .build(); HttpResponse response = client.send( request, HttpResponse.BodyHandlers.ofString() ); System.out.println(response.body()); } } ``` **After (Rust):** ```rust use reqwest; #[tokio::main] async fn main() -> Result<(), reqwest::Error> { let response = reqwest::get("https://api.example.com/data") .await? .text() .await?; println!("{}", response); Ok(()) } ``` ### Example 2: Medium - Data Processing Pipeline **Before (Java):** ```java import java.util.*; import java.util.stream.*; public class DataProcessor { public static class User { String name; int age; String city; public User(String name, int age, String city) { this.name = name; this.age = age; this.city = city; } } public static List processUsers(List users) { return users.stream() .filter(user -> user.age >= 18) .filter(user -> user.city.equals("NYC")) .map(user -> user.name.toUpperCase()) .sorted() .collect(Collectors.toList()); } public static void main(String[] args) { List users = Arrays.asList( new User("Alice", 25, "NYC"), new User("Bob", 17, "LA"), new User("Charlie", 30, "NYC") ); List result = processUsers(users); System.out.println(result); } } ``` **After (Rust):** ```rust #[derive(Debug)] struct User { name: String, age: u32, city: String, } fn process_users(users: &[User]) -> Vec { users .iter() .filter(|user| user.age >= 18) .filter(|user| user.city == "NYC") .map(|user| user.name.to_uppercase()) .collect::>() .into_iter() .sorted() .collect() } fn main() { let users = vec![ User { name: String::from("Alice"), age: 25, city: String::from("NYC"), }, User { name: String::from("Bob"), age: 17, city: String::from("LA"), }, User { name: String::from("Charlie"), age: 30, city: String::from("NYC"), }, ]; let result = process_users(&users); println!("{:?}", result); } ``` ### Example 3: Complex - Concurrent Web Server **Before (Java):** ```java import java.io.*; import java.net.*; import java.util.concurrent.*; public class SimpleServer { private static final ExecutorService executor = Executors.newFixedThreadPool(10); public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("Server started on port 8080"); while (true) { Socket clientSocket = serverSocket.accept(); executor.submit(() -> handleClient(clientSocket)); } } private static void handleClient(Socket socket) { try ( BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); PrintWriter out = new PrintWriter( socket.getOutputStream(), true ) ) { String request = in.readLine(); System.out.println("Request: " + request); String response = processRequest(request); out.println("HTTP/1.1 200 OK"); out.println("Content-Type: text/plain"); out.println(); out.println(response); } catch (IOException e) { System.err.println("Error handling client: " + e.getMessage()); } } private static String processRequest(String request) { // Simulate processing return "Processed: " + request; } } ``` **After (Rust):** ```rust use tokio::net::{TcpListener, TcpStream}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; #[tokio::main] async fn main() -> Result<(), Box> { let listener = TcpListener::bind("127.0.0.1:8080").await?; println!("Server started on port 8080"); loop { let (socket, _) = listener.accept().await?; tokio::spawn(async move { if let Err(e) = handle_client(socket).await { eprintln!("Error handling client: {}", e); } }); } } async fn handle_client(socket: TcpStream) -> Result<(), Box> { let mut reader = BufReader::new(socket); let mut request = String::new(); reader.read_line(&mut request).await?; println!("Request: {}", request.trim()); let response = process_request(&request); let mut socket = reader.into_inner(); socket.write_all(b"HTTP/1.1 200 OK\r\n").await?; socket.write_all(b"Content-Type: text/plain\r\n").await?; socket.write_all(b"\r\n").await?; socket.write_all(response.as_bytes()).await?; Ok(()) } fn process_request(request: &str) -> String { format!("Processed: {}", request.trim()) } ``` **Why this translation:** - Tokio provides async I/O (more efficient than thread pool) - `async`/`await` syntax is more readable - Type-safe error handling with `Result` - Automatic resource cleanup (no explicit try-with-resources) - Lightweight async tasks instead of OS threads --- ## See Also For more examples and patterns, see: - `meta-convert-dev` - Foundational patterns with cross-language examples - `convert-golang-rust` - Similar GC → ownership translation - `convert-python-rust` - Dynamic → static typing translation - `lang-java-dev` - Java development patterns - `lang-rust-dev` - Rust development patterns Cross-cutting pattern skills: - `patterns-concurrency-dev` - Async, threads, channels across languages - `patterns-serialization-dev` - JSON, validation, annotations across languages - `patterns-metaprogramming-dev` - Annotations, macros, reflection across languages