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()); }