'Method return type `()` vs `Self`
I'm doing the Clock exercise from Exercism, which is about implementing a clock that handles times without dates.
In it I need to write a method add_minutes, which is simply adding minutes to a clock.
I come from Java so took it the Java way, making add_minutess a method with () return type.
However, the exercise designs the method with Self return type.
I'm not sure why is it designed this way and what benefits it has over my () method?
use std::fmt;
#[derive(Eq, PartialEq, Debug)]
pub struct Clock {
minutes: i32,
}
impl Clock {
pub fn new(hour: i32, minute: i32) -> Self {
Clock::build(hour * 60 + minute)
}
// Self type
pub fn add_minutes(&mut self, minutes: i32) -> Self {
Clock::build(self.minutes + minutes)
}
// void type
pub fn add_minutes_void(&mut self, minutes: i32) {
self.minutes = self.minutes + minutes
}
fn build(minutes: i32) -> Self {
let mut mins = minutes;
while mins < 0 {
mins += 1440;
}
Clock {
minutes: mins % 1440,
}
}
}
impl fmt::Display for Clock {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let hours = self.minutes / 60;
let mins = self.minutes % 60;
write!(f, "{:02}:{:02}", hours, mins)
}
}
Test case for add_minutes:
#[test]
fn test_add_minutes() {
let clock = Clock::new(10, 0).add_minutes(3);
assert_eq!(clock.to_string(), "10:03");
}
Test case for add_minutes_void:
#[test]
fn test_add_minutes_void() {
let mut clock = Clock::new(10, 0);
clock.add_minutes_void(3);
assert_eq!(clock.to_string(), "10:03");
}
Solution 1:[1]
Whether the method should return () or Self depends on whether you want the method to mutate the value it is called on (like +=) or leave that value alone and return a new value (like +).
Since this is a shallow type (it's a single i32 with no heap-allocated data) it makes more sense to have such a method return a new value and leave the original alone, in which case you can take &self instead of &mut self.
In fact, I'd suggest adding Copy and Clone to the derive macro for Clock and then this method can become a consuming method (taking self as a value instead of a reference):
// Self type
pub fn add_minutes(self, minutes: i32) -> Self {
Clock::build(self.minutes + minutes)
}
Why is this better? It gives you more ways to use the method:
Mutating the value by taking
&mut selfexcludes all other access to the value, which may be undesirable and/or impossible in some cases, such as when chaining closure-taking iterator methods together.Mutating the value also makes it more verbose when you need to retain the original value:
let copy = existing; // Assuming we have #[derive(Copy)] existing.add_minutes(60);Not mutating the value enables all of the exact same operations, but they are either simpler or largely unchanged. For example, the above example would become:
let copy = existing.add_minutes(60);Similarly, mutating an existing value just requires assignment to the original value:
existing = existing.add_minutes(60);
Solution 2:[2]
It's a different API design. Both of these can achieve the same things.
add_minutes works on an immutable reference. As such to use it you don't need exclusive access to the Clock instance. add_minutes_void takes a &mut reference, so it needs exclusive access to the instance.
These are equivalent:
- To make a cloned version that had minutes added:
let cloned = existing.clone();
cloned.add_minutes_void(60);
// Or
let cloned = existing.add_minutes(60);
- To set the existing variable:
existing.add_minutes_void(60);
// Or
existing = existing.add_minutes(60);
The second style is often shorter in rust.
There is another thing to consider though: Performance. Usually the compiler is able to optimize out the move (rustc is very inline happy). And even if it can't, memcopy is really fast these days. But in super performance critical paths, using the mutable variant might be a good idea.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | cdhowie |
| Solution 2 | Misty |
