Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Special pages
JOHNWICK
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Rust for Java Developers
Page
Discussion
English
Read
Edit
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
View history
General
What links here
Related changes
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
If you come from a Java background, you’re used to interfaces, inheritance, and garbage collection taking care of memory. Rust re-imagines these ideas — favoring compile-time safety and performance without a garbage collector. This post bridges that gap so you can map familiar Java concepts to Rust’s world. Basic Concepts in Rust Traits in Rust are conceptually like a Java interface. A trait specifies a set of method signatures that must be implemented. // Defines a summary interface trait Summary { fn summarize(&self) -> String; } A Type in Rust defines the structure and characteristics of a value. Rust is a statically-typed language, meaning the type of every variable is known at compile time. You can define custom types using Structs and Enums. // Define a struct (the data type) struct NewsArticle { headline: String, location: String, } // Define a second struct (the data type) struct Tweet { username: String, content: String, } When a struct implements a trait, it means the struct provides concrete implementations for all the methods defined in that trait. // Struct NewsArticle implements Summary interface impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by our correspondent in {}", self.headline, self.location) } } // Struct Tweet implements Summary interface impl Summary for Tweet { fn summarize(&self) -> String { format!("@{}: {}", self.username, self.content) } } Polymorphism (“many forms”) is the ability to treat objects of different types / structs uniformly. A function can accept any structure which implements a given trait. The below syntax means, Item is of type T, and T must implement the Summary trait. // The function can accept any type T that implements the 'Summary' interface fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize()); } Static & Dynamic Dispatch In programming, dispatch means how a program decides which exact function implementation to call when a method is invoked on an object. Static Dispatch (Compile Time) When you compile this, Rust generates two specialized versions of notify — one for NewsArticle, one for Tweet. This process is called monomorphization. The advantage is zero runtime overhead. Monomorphization (from “mono” = single, and “morph” = form/type) is the process Rust uses to turn generic code into specialized concrete code for each type that you use it with — effectively “baking in” the type parameters at compile time. fn main() { let article = NewsArticle { headline: String::from("Mars Mission Successful"), location: String::from("Bangalore"), }; let tweet = Tweet { username: String::from("astro_kalpana"), content: String::from("We’ve landed on Mars!"), }; notify(&article); notify(&tweet); } Dynamic Dispatch (Runtime) Here, notify_dynamic doesn’t know what concrete type item is — it could be any type implementing Summary. Every struct in Rust stores a pointer to data inside the struct, and also a pointer to the functions it has implemented (vtable — virtual function table). At runtime, Rust looks up the correct method via the vtable and calls it. fn notify_dynamic(item: &dyn Summary) { println!("(Dynamic) Breaking news! {}", item.summarize()); } fn main() { let article = NewsArticle { headline: String::from("Ocean Cleanup Succeeds"), location: String::from("San Francisco"), }; let tweet = Tweet { username: String::from("eco_warrior"), content: String::from("Over 100 tons of plastic removed from the ocean."), }; // Both types share a common trait reference let items: Vec<&dyn Summary> = vec![&article, &tweet]; for item in items { notify_dynamic(item); // Uses vtable to resolve the correct method } } Memory Safety in Rust The borrower (or borrowing) concept in Rust is central to how the language enforces memory safety without a garbage collector. Every value in Rust has one owner — the variable that holds it. When that variable goes out of scope, the memory is automatically freed. fn main() { let s = String::from("hello"); // s owns this String, main owns s println!("{}", s); } // s goes out of scope here → String memory is freed But what if you want to let another function use that value without taking ownership of it? Borrowing means temporarily accessing someone else’s data without becoming its owner. You do this using references: &T for immutable borrows and &mut T for mutable borrows. You can have any number of immutable (&T) borrows or exactly one mutable (&mut T) borrow at a time — never both. Immutable Borrow (Read-Only) * The ownership of s stays with main. * print_length borrows s immutably using &String. * After the function call, you can still use s because you never transferred ownership. fn print_length(s: &String) { println!("Length is {}", s.len()); } fn main() { let s = String::from("hello"); print_length(&s); // Borrow `s` (read-only) println!("{}", s); // Still can use s afterward } Mutable Borrow (Read + Write) * While s is borrowed mutably, no one else (not even the owner) can access s. fn append_world(s: &mut String) { s.push_str(" world"); } fn main() { let mut s = String::from("hello"); append_world(&mut s); // Mutable borrow println!("{}", s); } Heap Cleanup in Rust * When Rust drops a reference, it also drops the value it points to. // Java Example void main() { String s = new String("hello"); } // s reference disappears, but actual memory freed later by GC // Rust Example fn main() { let s = String::from("hello"); } // s dropped immediately here, heap freed right now Compile Time Errors * Rust’s borrow checker ensures that no reference outlives its owner. let r; { let s = String::from("hello"); r = &s; // ❌ r will live, s will go out of scope, rust error here } println!("{}", r); * Syntax: <'a> (read as “for some lifetime 'a”). In below example, the function promises: “the returned reference will be valid for as long as both x and y are valid.” fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } let s1 = String::from("hello"); let result; { let s2 = String::from("world"); // ❌ result lives, s2 goes out of scope result = longest(s1.as_str(), s2.as_str()); // error out here } println!("Longest is {}", result); * If you create an immutable reference (borrow) to s. You can read s through r1. But as long as this immutable borrow is active, no one else can modify s. If you create a mutable reference to the same variable s while the immutable reference r1 still exists, the compiler sees that r1 might be used in the same scope as r2 . Because allowing bothr1 (a reader) and r2 (a writer) at the same time could lead to a data race — one part of the code could be reading while another is writing. let mut s = String::from("hello"); let r1 = &s; let r2 = &mut s; // ❌ cannot borrow as mutable while immutable borrow exists println!("{} {}", r1, r2); * You can fix the above by making sure the immutable borrow r1 is no longer used when you create the mutable one. fn main() { let mut s = String::from("hello"); { let r1 = &s; // immutable borrow println!("{}", r1); // used here } // r1 goes out of scope here let r2 = &mut s; // now mutable borrow is allowed r2.push_str(" world"); println!("{}", r2); } Multi Threading in Rust In Rust, ownership and borrowing rules are enforced across threads too. In the below example, v belongs to the main thread. The spawned thread might outlive it, leading to a dangling reference. Rust refuses to allow that. use std::thread; fn main() { let v = vec![1, 2, 3]; thread::spawn(|| { // ❌ Error: `v` does not live long enough println!("{:?}", v); }); } Note, || syntax in Rust is how you define a closure, which is Rust’s equivalent of a lambda expression in Java. A closure is an anonymous function that can capture variables from the surrounding scope. In above case there is no variable. Here is an example below, let add = |x, y| x + y; println!("{}", add(2, 3)); // prints 5 Closures can capture variables from their surrounding environment automatically — by reference, mutable reference, or by value — depending on how you use them. let v = vec![1, 2, 3]; let print_v = || println!("{:?}", v); print_v(); But inside a new thread, Rust needs to ensure safety. If the spawned thread might outlive v, the compiler complains — hence you must use move to explicitly transfer ownership. Now the closure takes ownership of v, and Rust knows it’s safe. use std::thread; fn main() { let v = vec![1, 2, 3]; thread::spawn(move || { // ❌ Error: `v` does not live long enough println!("{:?}", v); }); }
Summary:
Please note that all contributions to JOHNWICK may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
JOHNWICK:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Search
Search
Editing
Rust for Java Developers
Add topic