Thomas Wileman

Machine Learning Engineer & Technical Writer

Comparing Python to Go, Rust, and JavaScript: Class Instantiation

I've been developing almost exclusively in Python for the past six years, primarily focusing on data engineering and machine learning tasks. While I'm confident in my ability to write effective and reliable Python code, my knowledge tends to be practical and focused on solving specific problems. However, I recognize an opportunity to deepen my theoretical understanding of Python's internals and foundational concepts, particularly when it comes to articulating details about the language's underlying mechanics.

Part of this stems from my practical, rather than theoretical, approach to Python—I haven't yet invested significant time into mastering its deeper structures and nuances. And a big component of that is I don't have anything to compare Python against. I don't know how Go, Rust, or another language would read in data, transform it, and output a result. I want to change that.

So for the next few weeks I'm going to look at how Python, Go, Rust, and JavaScript would all solve the same programming tasks. I'll first write that task in each language, make some notes about the syntax used, and then compare the differences between them.

The first programming task I'm going to examine is class instantiation. Class instantiation is simply the creation of an object. That object will almost certainly have attributes and will usually be instantiated multiple times to handle different versions of the same class.

We're going to create a class called, "Person". This class will have two attributes, name and age. We'll then instantiate an instance of this class for a 30 year old person named Alice. We'll also give this class a method called Greet which will return, "Hello, I'm Alice".

Dunder Methods (__init__)
The "self" Parameter
Instance Attributes
F-String Formatting
Python Implementation
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def greet(self):
        return f"Hello, I'm {self.name}"
person = Person("Alice", 30)
print(person.greet())
Struct vs Impl Separation
Explicit Type Declarations
Constructor Convention
Borrowing with &self
Rust Implementation
struct Person {
    name: String,
    age: u32,
}
impl Person {
    fn new(name: String, age: u32) -> Person {
        Person { name, age }
    }
    fn greet(&self) -> String {
        format!("Hello, I'm {}", self.name)
    }
}
let person = Person::new("Alice".to_string(), 30);
println!("{}", person.greet());
Package Declaration
Pointers and Memory
Method Receivers
Explicit Typing Everywhere
Go Implementation
package main
import "fmt"
type Person struct {
    Name string
    Age  int
}
func NewPerson(name string, age int) *Person {
    return &Person{
        Name: name,
        Age:  age,
    }
}
func (p *Person) Greet() string {
    return fmt.Sprintf("Hello, I'm %s", p.Name)
}
person := NewPerson("Alice", 30)
fmt.Println(person.Greet())
Constructor Method
Implicit 'this' Context
Template Literals
No Type Checking
JavaScript Implementation
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    greet() {
        return `Hello, I'm ${this.name}`;
    }
}
const person = new Person("Alice", 30);
console.log(person.greet());

Takeaways

Python and Rust are my favorite. Python is easy to read and write. We can also include some aspects of Rust and Go (like data structure/method separation and type checking) that result in extensible and reliable code.

Rust appears pretty intuitive once you understand the structure/method separation concept and I appreciate the requirement for data types when defining a structure without being super verbose as it appears Go requires.