Exploring Rust Syntax and Semantics from a Java Developer's Perspective

Getting Started

Modern systems programming increasingly relies on languages that offer memory safety without garbage collection. Rust, with its strict compiler and unique ownership model, stands out in this landscape. For developers familiar with Java, Rust presents a different paradigm where concepts like immutability by default and memory safety are enforced at compile time rather than runtime. This comparison not only highlights the differences in syntax but also offers insights into how language design influences software architecture and reliability.

Hello World and Macros

Executing basic Rust code is straightforward using the official online Playground. The canonical entry point looks like this:

fn main() {
    println!("Greetings from Rust!");
}

The fn main() function serves as the program entry, analogous to Java's public static void main(String[] args). Unlike a standard method call, println! is a macro, indicated by the exclamation mark. Macros in Rust operate at the syntactic level, expanding code during compilation. This mechanism allows for features like variadic arguments, which the language syntax itself does not support natively.

fn main() {
    let user = "Alice";
    let count = 42;
    println!("User: {}, Count: {}", user, count);
}

This string interpolation resembles logging libraries in Java (e.g., SLF4J), but in Rust, the macro generates the necessary type-checking and formatting logic at compile time.

Variable Bindings and Types

Variables are declared using the let keyword, establishing a "binding" between a name and a value. Type inference is the default, though types can be explicitly annotated.

fn main() {
    let x = 10; // Inferred as i32
    let y: i64 = 20; // Explicit i64
    println!("Values: {}, {}", x, y);
}

Rust provides a rich set of primitive numeric types, distinguishing between signed (i*) and unsigned (u*) integers, similar to C/C++ but unlike Java, which lacks unsigned primitive types (except for char).

let small: u8 = 255;     // 8-bit unsigned
let standard: i32 = 100; // 32-bit signed
let big: u64 = 10_000;   // 64-bit unsigned
let pointer_size: usize = 100; // Architecture dependent

Type conversion is strict. Implicit widening conversions common in Java are not permitted; explicit casting using the as keyword is required.

let base: i32 = 5;
let widened: i64 = base as i64;
let narrowed: i32 = widened as i32; // Potential data loss

Handling Unused Variables

Rust prioritizes code hygiene by emitting warnings for unused variables, imports, or functions. To suppress these warnings without removing the code, prefix the variable name with an underscore.

fn main() {
    let _active = true; // No warning
    let _data = vec; // No warning
}

This convention signals intent to the compiler and other developers that the variable is intentionally being ignored for the time being.

Immutability by Default

A significant departure from Java is Rust's default behavior regarding mutability. Variables are immutable by default. Attempting to reassign a value results in a compilation error.

fn main() {
    let score = 10;
    score = 20; // Error: cannot assign twice to immutable variable
}

To allow modification, the mut keyword must be explicitly added to the binding.

fn main() {
    let mut score = 10;
    score = 20; // OK
    println!("Score: {}", score);
}

This design philosophy encourages safer code. In large Java codebases, tracking mutations of objects passed through various methods can be difficult. Rust's requirement for explicit mutability increases readability and limits the scope where state changes can occur, thereby reducing the surface area for bugs. Furthermore, explicit immutability allows the compiler to perform aggressive optimizations, such as placing values in registers and reordering instructions, knowing the data will not change unexpectedly.

Tags: rust java Programming Languages Syntax Memory Safety

Posted on Sat, 06 Jun 2026 16:10:00 +0000 by martor