Originally published on php2rust.dev — part of the PHP to Rust course. View this lesson on the original site.
In PHP, a class usually bundles properties and methods in one class Rectangle { ... } block. Rust splits that into two pieces: a struct (or enum) for data, and one or more impl TypeName { ... } blocks for functions that belong to that type. The ideas map well: instance methods ≈ methods with $this, and associated functions (no self) ≈ static methods.
Methods: $this becomes explicit self
In PHP, $this is implicit inside instance methods. In Rust, the receiver is the first parameter, with special syntax:
| Rust receiver | Rough PHP mental model |
|---|---|
&self |
Read-only use of $this (you don’t reassign $this, you only read properties / call other methods). |
&mut self |
Mutating $this’s fields (like changing properties on the object). |
self |
Taking ownership of the value (no direct PHP equivalent for “value types”; common when a method consumes the struct, e.g. builder patterns). |
Calling rect.area() is syntactic sugar: the compiler turns it into something like Rectangle::area(&rect), so you can always think “method = function that takes the instance as first argument.”
Associated functions: like public static function
If a function in an impl block does not take self, &self, or &mut self, it is an associated function, not called on an instance with ., but on the type with ::.
That matches PHP static methods:
- PHP:
Rectangle::square(3) - Rust:
Rectangle::square(3)
Inside impl Rectangle, Self means “this type” (here, Rectangle), similar to PHP’s self return type for new self(...).
. vs :: (quick rule)
value.method(...), instance method (hasself/&self/&mut self).Type::name(...), associated function or explicitly calling a method with the type on the left (e.g.Rectangle::area(&rect)).
Why impl is separate from struct
You’ll often see struct + impl back-to-back in one file; it still isn’t one keyword like class. That separation matters later: you can have several impl blocks for the same type, split methods across modules, and control visibility (pub) on methods and fields independently, similar in spirit to choosing what’s public on a PHP class, but enforced by the compiler.
Rust
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
fn square(size: u32) -> Self {
Rectangle { width: size, height: size }
}
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("Area: {}", rect.area());
let sq = Rectangle::square(3);
println!("Square area: {}", sq.area());
println!("rect can hold sq? {}", rect.can_hold(&sq));
}
PHP
<?php
class Rectangle {
public function __construct(
public int $width,
public int $height,
) {}
public function area(): int {
return $this->width * $this->height;
}
public function canHold(Rectangle $other): bool {
return $this->width > $other->width && $this->height > $other->height;
}
public static function square(int $size): self {
return new self($size, $size);
}
}
$rect = new Rectangle(30, 50);
echo "Area: {$rect->area()}\n";
$sq = Rectangle::square(3);
echo "Square area: {$sq->area()}\n";
echo 'rect can hold sq? ' . ($rect->canHold($sq) ? "true\n" : "false\n");
Comparison
- Rust: Data in
struct, behaviour inimpl Type { ... }. Receiver explicit:&self/&mut self/self. Nothiskeyword. - PHP: Data + behaviour in one
class.$thisimplicit in instance methods;staticfor type-level functions. - Rust: Functions without
selfare associated functions, called withType::name(...). - PHP:
staticmethods are the closest match; call withType::name(...)orstatic::from inside the class. - Rust:
Selfinimpl Rectangle= the type being implemented (like PHPselffor return/static factory).
References
What's next
Next: Enums.
From PHP to Rust — Rust Syntax and Functions