We first examined how Python, Go, Rust, and JavaScript handle class instantiation. The items that stick with me from that assessment were how Rust separates data structures from methods and Go's strict type system.
In this second look, we'll see how these languages use simple methods to operate on instantiated objects. For each language we'll implement the following types of methods in the Person class we introduced previously:
- A getter method that accesses the person's age
- A setter method that modifies the person's age
- A method that modifies the state of an object by increasing their age
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# Getter method
def get_age(self):
return self.age
# Setter method
def set_age(self, new_age):
self.age = new_age
# Method that modifies state
def have_birthday(self):
self.age += 1
return f"Happy birthday! Now I'm {self.age}"
# Usage
person = Person("Alice", 30)
print(person.get_age()) # 30
print(person.have_birthday()) # Happy birthday! Now I'm 31
person.set_age(25)
print(person.get_age()) # 25
Python Analysis
- The first thing I notice is how clean and readable Python is. The logic behind each method is easily understandable.
- Again, we see that variable types (name and age) don't require data types when defined. Python figures datatypes out at runtime which makes it easier during development but may result in runtime errors that other languages might have caught earlier.
- The setter method,
set_age
, makes it easy to change the age of an established Person object. - Similarly, the
have_birthday
method shows us how easy Python can modify an established state. - The getter method,
get_age
, is redundant. We could just reference the age attribute of an instantiated object.
struct Person {
name: String,
age: u32,
}
impl Person {
// Constructor
fn new(name: String, age: u32) -> Person {
Person { name, age }
}
// Getter method (borrows self)
fn get_age(&self) -> u32 {
self.age
}
// Setter method (mutable borrow)
fn set_age(&mut self, new_age: u32) {
self.age = new_age;
}
// Method that modifies state
fn have_birthday(&mut self) -> String {
self.age += 1;
format!("Happy birthday! Now I'm {}", self.age)
}
}
// Usage
let mut person = Person::new("Alice".to_string(), 30);
println!("{}", person.get_age()); // 30
println!("{}", person.have_birthday()); // Happy birthday! Now I'm 31
person.set_age(25);
println!("{}", person.get_age()); // 25
Rust Analysis
- Off the bat, the struct/impl pattern separates data definition from behavior. This means that when the code compiles, Rust doesn't have to guess about data types or lookup which function to apply to the object. Datatypes are already resolved and the compiler directly inserts the function call.
- Rust uses
&self
and&mut self
for memory management.&self
reads instantiated object data but doesn't change it.&mut self
reads and modifies instantiated data. Python doesn't have this. Instead Python uses a garbage collector to track memory. This makes it easy to use but slow. Rust enforces ownership rules at compile time to manage memory.
package main
import "fmt"
type Person struct {
Name string
Age int
}
// Constructor function
func NewPerson(name string, age int) *Person {
return &Person{Name: name, Age: age}
}
// Getter method
func (p *Person) GetAge() int {
return p.Age
}
// Setter method
func (p *Person) SetAge(newAge int) {
p.Age = newAge
}
// Method that modifies state
func (p *Person) HaveBirthday() string {
p.Age++
return fmt.Sprintf("Happy birthday! Now I'm %d", p.Age)
}
// Usage
func main() {
person := NewPerson("Alice", 30)
fmt.Println(person.GetAge()) // 30
fmt.Println(person.HaveBirthday()) // Happy birthday! Now I'm 31
person.SetAge(25)
fmt.Println(person.GetAge()) // 25
}
Go Analysis
- The
*Person
and&Person
give you control over memory. The&
means "address of" and allows you to allocate memory. The*
means that a function points to a specific Person object.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Getter method
getAge() {
return this.age;
}
// Setter method
setAge(newAge) {
this.age = newAge;
}
// Method that modifies state
haveBirthday() {
this.age++;
return `Happy birthday! Now I'm ${this.age}`;
}
}
// Usage
const person = new Person("Alice", 30);
console.log(person.getAge()); // 30
console.log(person.haveBirthday()); // Happy birthday! Now I'm 31
person.setAge(25);
console.log(person.getAge()); // 25
JavaScript Analysis
- Again, the syntax is very similar to Python. No type declarations, OOP class syntax, and easily understandable string handling. Further reading makes it clear that JavaScript is meant for web development. They share similar design philosophies: readable, maintainable code, rapid development support.
Key Takeaways
Rust's ownership system, &self
and &mut self
, and Go's memory pointer system, &Person
and *Person
, give you explicit control over memory. However they handle data concurrency differently. Rust won't compile if there are type errors or data races. On the other hand Go doesn't prevent a data object from being accessed by two operations at the same time. You have to use additional tools to enforce sequential operations.
Python and JavaScript employ some of the same philosophies but for different use cases (Python for data applications and JavaScript for web applications).