If you write Rust, you interact with &self and &mut self every single day. They are the backbone of idiomatic Rust, signaling at a glance whether a method reads from or mutates an instance.
However, Rust’s ownership and type system goes deeper than just standard references. Tucked inside the language specification is arbitrary self types—allowing specialized receivers like self: &Arc and self: &Mutex.
While rare in everyday application code, these self-types are power tools when architecting concurrent and multi-threaded systems.
The Standard Receivers: A Quick Refresh
Before diving into the exotic variants, here is how the standard receivers behave:
&self (Shared Reference): Borrow the instance immutably. Multiple callers can execute this method simultaneously without mutating state.
&mut self (Exclusive Reference): Borrow the instance mutably. Guarantees exclusive access to modify the object's internal fields.
self (By Value): Consume the instance, taking full ownership and moving it into the method.
The Power Receivers: &Arc and &Mutex
In concurrent Rust, state is frequently wrapped inside thread-safe smart pointers or synchronization primitives—most notably Arc<T> (Atomic Reference Counting) and Mutex<T>.
Normally, calling a method on an Arc requires cloning the Arc explicitly before calling a standard method:
// Traditional approach
let shared_node = Arc::new(MyNode::new());
MyNode::process_node(shared_node.clone());
By explicit receiver typing, you can make the method itself demand the smart-pointer wrapper directly on self:
impl MyNode {
// Requires the caller to pass a reference to the Arc wrapping this instance
pub fn spawn_task(self: &Arc<Self>) {
let clone = Arc::clone(self);
tokio::spawn(async move {
clone.do_work();
});
}
}
Top comments (0)