Reputation: 2961
I'm trying to implement the Rust equivalent code of the following C++ code which makes use of inheritance, but got stuck. This is my sample code:
class Vehicle {
public:
double lat;
double lon;
double alt;
double speed;
};
class CabVehicle : public Vehicle {
};
class PackerMoverVehicle : public Vehicle {
};
int main() {
CabVehicle cv;
cv.lat = 12.34;
cv.lon = 12.34;
cv.alt = 12.34;
PackerMoverVehicle pmv;
pmv.lat = 12.34;
pmv.lon = 12.34;
pmv.alt = 12.34;
}
How should this be written in Rust?
Upvotes: 4
Views: 1530
Reputation: 43753
The general answer is to use composition instead of inheritance. Depending on the application, there can be different ways the composition should go. In most cases, you should start with
struct VehicleState {
lat: f64,
lon: f64,
alt: f64,
speed: f64,
}
The remaining question then is how your different types of vehicles are going to be used.
Way 1: If different parts of the code use the different types of vehicles in distinct, non-overlapping ways, you might simply contain the state struct in the specific structs:
struct Cab {
state: VehicleState,
// ... other fields
}
struct PackerMover {
state: VehicleState,
// ... other fields
}
This is the version most directly analogous to C++ inheritance, particularly in memory layout and in static typing. However, this makes it awkward to access the common state
for different vehicles, and it does not support dynamic dispatch, unless you write a trait with a method to access state
(which comes with some limitations in the kinds of code you can write). You should generally avoid this approach unless you know you don't need anything else.
Way 2: If there is code which should be generic over which kind of vehicle is in use, but this is statically decided, you might make a generic struct:
struct Vehicle<T> {
state: VehicleState,
details: T,
}
struct Cab { /* ... */ }
struct PackerMover { /* ... */ }
/// This function only works with Cabs
fn foo(vehicle: Vehicle<Cab>) { /* ... */ }
/// This function works with any Vehicle
fn foo<T>(vehicle: Vehicle<T>) { /* ... */ }
This makes it easy to access the state
, and all usage is statically dispatched.
It can be dynamically dispatched too if you make one small change to Vehicle
and add a trait:
struct Vehicle<T: ?Sized> { /* ... */
// ^^^^^^^^ remove default restriction on the type parameter
trait VehicleDetails { /* add methods here */ }
impl VehicleDetails for Cab { /* ... */ }
impl VehicleDetails for PackerMover { /* ... */ }
This allows you to coerce a reference (or pointer or Box
too) &Vehicle<Cab>
into &Vehicle<dyn VehicleDetails>
, which is a type that a pointer to any Vehicle
whose T
implements VehicleDetails
. This can be used to put a variety of vehicles in a Vec<Box<Vehicle<dyn VehicleDetails>>>
, for example. Using dyn
causes dispatch through vtables, like C++ virtual methods.
(Info on this language feature. The documentation says that “custom DSTs are a largely half-baked feature for now” but this particular case is exactly the case where they do work without any trouble.)
This is not a good choice if you want to be able to find out which “subclass” is being used and interact with it specifically; it is a good choice if all particular characteristics of the vehicle can be expressed within the VehicleDetails
trait.
Way 3: If the application is going to be routinely working with dynamically-chosen vehicle types — especially if it frequently wants to ask the question “is this vehicle a Cab
” and then interact with its Cab
ness — then you should probably use an enum
to contain the details.
struct Vehicle {
state: VehicleState,
kind: VehicleKind,
}
enum VehicleKind {
Cab {
seats: u16,
},
PackerMover {
cargo_capacity: u64,
}
}
This is dynamically dispatched in the sense that every Vehicle
can be any kind, so you can always mix-and-match vehicle kinds, but without involving any pointers or vtables. The main disadvantage is that extending it to new kinds requires modifying the single enum VehicleKind
, so this is not suitable for a library whose users would be writing subclasses in C++. However, this is a lot less fiddly to work with than the Vehicle<dyn VehicleDetails>
I mentioned above.
Upvotes: 4
Reputation: 998
Rust is basically more like a procedural and a functional language with some pseudo-OO features, it’s simultaneously lower-level and more abstract than C++ (closer to C or even to C—, but with ZCA and stronger typing). The answer is: there’s no inheritance in Rust, use composition or just rewrite the whole structure. This may look wild for you, but after some time you will understand that there’s no need in inheritance.
Upvotes: 0