Rust for Java developers: the minimum
You do not need to learn all of Rust to write a plugin. This page shows the part that the tutorials use, next to the Java you already know, and ends with links for when you want more. The Java snippets are illustrations and are not part of mcrs. The Rust snippets are short sketches too, and CI does not compile them.
The Rust you need, in Java terms
Section titled “The Rust you need, in Java terms”| Rust | Java idea |
|---|---|
struct, enum |
class with fields, enum that can carry data |
impl block |
Methods of a class |
trait |
interface |
Option<T> |
A value that may be null |
Result<T, E> and ? |
Checked exceptions |
match |
switch that must cover every case |
Closure |x| ... |
Lambda |
Vec, HashMap, String and &str |
List, Map, String |
#[derive(Clone)] |
Generated copy and equals code |
&T and &mut T |
Many readers or one writer, checked at compile time |
Modules, use, Cargo.toml |
Packages, imports, build.gradle |
You can leave unsafe, procedural macros and async for later. The same goes for lifetimes, except that you will meet them when you read signatures that other people wrote.
The same code in Java and in Rust
Section titled “The same code in Java and in Rust”A Java class becomes two things in Rust: a struct for the data and an impl block for the methods.
// a class with data and a methodclass Counter { private int ticks; void tick() { ticks++; }}// a struct with data, and an impl block with a methodstruct Counter { ticks: u32,}
impl Counter { fn tick(&mut self) { self.ticks += 1; }}A Paper plugin extends JavaPlugin, and an mcrs plugin implements the Plugin trait. The #[derive(Clone)] line is there because every dimension gets its own copy of the plugin.
// a plugin is an interface implementationpublic class MyPlugin extends JavaPlugin { @Override public void onEnable() { /* register things */ }}// a plugin is a trait implementation; it must be Clone for dimensions#[derive(Clone)]struct MyPlugin;
impl Plugin for MyPlugin { fn build(&self, app: &mut App) { // register systems, resources, observers }}Where Java returns null or throws an exception, Rust returns an Option or a Result, so both cases are visible in the type. The ? operator returns the error to the caller.
// null for "no value", exceptions for errorsPlayer p = lookup(name);if (p == null) return;int n = Integer.parseInt(text); // may throw// Option and Result make both cases visible in the typelet Some(p) = lookup(name) else { return };let n: i32 = text.parse()?; // returns the error to the callerA closure does the job of a lambda. In the Rust tab the closure is an observer, which is a function that runs when an event is triggered.
// a lambda as a handlerevents.on(ChatEvent.class, e -> e.setCancelled(true));// a closure as a handler (an observer)app.add_observer(|mut e: On<PlayerChatEvent>| { e.cancelled = true;});The next example is a real plugin from the repository. It has a resource and a system that runs in FixedUpdate.
#[derive(Resource, Default)]struct Ticks<const ALL: bool>(u32);
#[derive(Message)]struct Ticked;
#[derive(Clone)]struct CountTicks<const ALL: bool>;
impl<const ALL: bool> Plugin for CountTicks<ALL> { fn name(&self) -> &str { if ALL { "count every tick" } else { "count overworld ticks" } }
fn build(&self, app: &mut App) { app.init_resource::<Ticks<ALL>>().add_systems( FixedUpdate, |mut ticks: ResMut<Ticks<ALL>>, mut ticked: MessageWriter<Ticked>| { ticks.0 += 1; if ALL { ticked.write(Ticked); } }, ); app.add_message::<Ticked>(); }}crates/mcrs_minecraft_server/tests/it/dimension_plugins.rs:10-40
A value has one owner
Section titled “A value has one owner”A value in Rust has one owner. The owner can lend it as &T to many readers or as &mut T to one writer, but never both at once. In a Bevy system you write this in the parameters, where Query<&T> reads and Query<&mut T> writes. You rarely write lifetimes.
Two habits change because of this. Keep an Entity id where you would keep a reference to a player, and call .clone() where you would share a value. Do not copy patterns from Rust servers built on Arc<Mutex<..>>, because the project avoids them in its library layers.1 The ownership chapter of the Rust Book explains the rules in full.
Three errors you will meet
Section titled “Three errors you will meet”| Error | Cause | Fix |
|---|---|---|
| Use of a moved value | You passed a value by value and then used it again | Pass &value or call .clone() |
| A function is not accepted as a system | One parameter has a type that Bevy cannot supply | Check that every parameter is Res, ResMut, Query, Commands, a message reader or writer, or Local |
Two parameters of one system want the same data, one of them as &mut |
The scheduler reports this when it builds the graph at startup, and the compiler does not catch it | Merge the queries or split the system |
Where to read more
Section titled “Where to read more”- In The Rust Programming Language, read the chapters on ownership, enums and
match, error handling and traits. - Rust by Example has short runnable examples.
- Rustlings is a set of small exercises.
- The Cargo guide covers
Cargo.tomland dependencies. - Bevy learn and the ECS examples introduce Bevy.
- The Unofficial Bevy Cheat Book can lag behind. mcrs uses Bevy 0.19, where the names are
Message,MessageReaderandOn<E>, and older text may use other names. - bevy_ecs on docs.rs has the exact signatures.
Next: your first plugin, then Paper and Minestom in mcrs terms.
Footnotes
Section titled “Footnotes”Sources and verification
Verified: 26.4-snapshot-7, 51d45c26a, 2026-11-09
- crates/mcrs_minecraft_server/tests/it/dimension_plugins.rs
- crates/mcrs_minecraft_engine_world/src/world/sub_app.rs
- rust-toolchain.toml
- issue #321
Tracking: #321