Migrate to Rust from C++

I'd recommend looking at the previous chapter, Migrating from C, before reading this chapter. It will give you a good understanding of the differences between C and Rust.

In this chapter, we will look at some of the differences between C++ and Rust.

Classes

Rust has no concept of classes. Instead, it has a concept of structs and impl blocks. Here's an example of a class in C++:

#include <iostream>

class MyClass {
public:
    int x;
    int y;

    MyClass(int x, int y) : x(x), y(y) {}

    int add() {
        return x + y;
    }
};

int main() {
    MyClass myClass(10, 20);
    std::cout << myClass.add() << std::endl;
}

In Rust, the same code would look like this:

struct MyClass {
    x: i32,
    y: i32,
}

impl MyClass {
    fn new(x: i32, y: i32) -> MyClass {
        MyClass { x, y }
    }

    fn add(&self) -> i32 {
        self.x + self.y
    }
}

fn main() {
    let my_class = MyClass::new(10, 20);
    println!("{:?}", my_class.add());
}

Although the code in Rust looks similar, there are a few differences:

  • In Rust, field and methods are private by default. You need to use the pub keyword to make them public.
  • In Rust, you need to use the impl block to define methods for a struct.
  • In Rust, you need to use the &self keyword to define methods that take a reference to the struct.
  • Rust has no concept of classes and inheritance. Instead, it has a concept of traits. We will look at traits in the next section.

Interfaces or Abstract Classes

In C++, you can define an interface using the virtual keyword. Here's an example of defining an interface in C++:

#include <iostream>

class GreeterInterface
{
public:
    virtual void greet() = 0;
};

class MyClass : public GreeterInterface
{
public:
    void greet() override
    {
        std::cout << "Hello, world!" << std::endl;
    }
};

int main()
{
    MyClass myClass;
    myClass.greet();
}

In Rust, the same code would look like this:

trait Greeter {
    fn greet(&self);
}

struct MyClass;

impl Greeter for MyClass {
    fn greet(&self) {
        println!("Hello, world!");
    }
}

fn main() {
    let my_class = MyClass;
    my_class.greet();
}

In Rust, you can define a trait using the trait keyword. You can then implement the trait for a struct using the impl block.

Passing Traits to Functions

Traits can be passed to functions in Rust in two ways:

  • Static dispatch: You can pass a trait to a function using a generic type parameter.
  • Dynamic dispatch: You can pass a trait to a function using a dynamic type parameter: &dyn.

With static dispatch, the compiler generates a separate version of the function for each type that implements the trait. With dynamic dispatch, the compiler generates a single version of the function that works with any type that implements the trait. The dynamic dispatch version is (marginally) slower, but it's more flexible.

trait Greeter {
    fn greet(&self);
}

struct MyClass;

impl Greeter for MyClass {
    fn greet(&self) {
        println!("Hello, world!");
    }
}

fn greet<T: Greeter>(greeter: &T) {
    greeter.greet();
}

fn dyn_greet(greeter: &dyn Greeter) {
    greeter.greet();
}

fn main() {
    let my_class = MyClass;
    greet(&my_class);
    dyn_greet(&my_class);
}

Inheritance

Rust has no concept of inheritance. Instead, it has a concept of composition. You can use composition to create hierarchies of objects. Here's an example of inheritance in C++:

#include <iostream>

class Animal {
public:
    virtual void speak() = 0;
};

class LandAnimal {
public:
    virtual void walk() = 0;
    int nrOfLegs() {
        return 4;
    }
};

class Dog : public Animal, public LandAnimal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }

    void walk() override {
        std::cout << "Walking on land" << std::endl;
    }
};

int main() {
    Dog dog;    
    dog.speak();
    dog.walk();
    std::cout << dog.nrOfLegs() << std::endl;
}

In Rust, the same code would look like this:

trait Animal {
    fn speak(&self);
}

trait LandAnimal {
    fn walk(&self);
    fn nr_of_legs(&self) -> i8 {
        4
    }
}

struct Dog;

impl Animal for Dog {
    fn speak(&self) {
        println!("Woof!");
    }
}

impl LandAnimal for Dog {
    fn walk(&self) {
        println!("Walking on land");
    }
}

fn main() {
    let dog = Dog;
    dog.speak();
    dog.walk();
    println!("{:?}", dog.nr_of_legs());
}