Type available within Rust supports composition of homogenous types, these are availabe in types such as
Vec<T>; // an vector
[T;N]; // an array
(); // tuple
which can be used depending on various scenarios required ,
though you can use it as a heterogenous type but it is naturally constrained by the Sized
trait which is applied to it during compile time which constrain us to further use it dynamically.
The key thing to note here is we can segregate our types based upon some common relation we can derive from them this will be the key idea to follow along towards achieving so
but when it comes to supporting the heterogenous types in rust things get little bit tricky here, to compose a type which can accommodate the objects from several types available using dyn
trait which points the object in vtable which is behind a pointer
you can read here,
we want our type to be resizable during runtime therefore I will choose to go with Vec<T>
type which can hold our objects
since whichever types applies this trait can directly reference a object using two techniques
1) using a reference to the object
Vec<&dyn yourTrait>;
2) placing the object behind a Box smart pointer
Vec<Box<dyn youTrait>>;
let's say the name of your trait is Foo
and it declares one method to be implement
trait Foo {
fn call(&self);
}
now we will create some random types which will implement this
are as follows
(for the sake of simplicity I have defined a tuple struct here but your type can be much more complicated than this ... as long as you implement the trait you should be fine)
#[derive(Debug)]
struct A(i32);
impl Foo for A {
fn call(&self) {
println!("type A: {:?}", self.0);
}
}
#[derive(Debug)]
struct B(i32);
impl Foo for B {
fn call(&self) {
println!("type B: {:?}", self.0);
}
}
#[derive(Debug)]
struct C(i32);
impl Foo for C {
fn call(&self) {
println!("type C: {:?}", self.0);
}
}
here you will observe one strange thing which is all the types implement the same exact definition of the trait methods but this can be different depending upon the operations you wanna do with that type.
now lets begin declaring a type which can simply hold them
according the first method
*1) using the direct reference to the object *
one important thing to note here is, the reference you gonna provide for your types will either be mutable or immutable only !
which simply mean, it applies to all the trait objects uniformly and u can't have some trait objects passed on as mutably and others as immutably.
fn main() {
//some random trait objects declared
let a = &A(43);
let b = &B(73);
let c = &C(95);
// now we will compose our homogenous type based on the previously types
let objs: Vec<&dyn Foo> = vec![a, b, c];
for I in objs.iter() {
I.call();
}
}
since our objects a, b, c
holds a reference to the types A, B, C
respectively we can pass them directly here to construct our homogenous type out of them
output :
type A: 43
type B: 73
type C: 95
2) Using Box smart pointer
now we will try out another interesting method, where we will place our objects behind a Box smart pointer in this case you don't have to worry about initializing your traits objects during compile itself, instead you will place them behind a pointer pointing to the memory location on heap
since in this technique the rules of borrow doesn't apply there it is quite convinence to work with
also the plus side is our objects can be init. during runtime which is a added advantage
fn main() {
//here we place our objects behind a box pointer
let a = Box::new(A(43));
let b = Box::new(B(73));
let c = Box::new(C(95));
//our type hold a boxed trait object as type to accept
let mut objs: Vec<Box<dyn Foo>> = vec![a,b,c] ;
for I in objs.iter() {
I.call();
}
}
here the output will be exactly same as the previous one but with more features of box pointer:
type A: 43
type B: 73
type C: 95
there you .. go congratulations you successfully learned how to create a new type >>
Thank you 😊 so much for reading till here 🥂🎉🎊!
if you have any comments, suggestion's, doubt or advice feel free to post in comment section
Top comments (0)