Custom Data Types With Struct, Trait, And Enum In Rust
We can create custom data types in Rust using struct, trait, and enum. A struct is a composite data type that groups variables in a memory block. These variables are then accessible via the struct’s instance – also referred to as an object in OOP languages. Moreover, a struct can have its methods (or functions) with implementations; and a trait can add new methods to it.
We can also use the
enum keyword to create custom data types whose values are generally only those predefined in its definition.
Custom Type With Struct
There are three ways to define a struct, but only 2 are generally useful – a struct with named fields and a struct with unnamed fields. When we declare a variable of a struct type and initialize it, we are creating an instance of that struct type.
Consider the codes below – we created instances of a struct type in lines 9 and 16.
main.rs
Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
structPerson{
last_name:String,
first_name:String,
company:String
}
fnmain(){
letperson_JohnLee=Person{
last_name:String::from("Lee"),
first_name:String::from("John"),
company:String::from("Turreta.com")
};
letperson_BartSimpson=Person{
last_name:String::from("Simpson"),
first_name:String::from("Bart"),
company:String::from("Turreta.com")
};
println!("Person 1: {} {} from {}",person_JohnLee.first_name,person_JohnLee.last_name,person_JohnLee.company);
println!("Person 2: {} {} from {}",person_BartSimpson.first_name,person_BartSimpson.last_name,person_BartSimpson.company);
}
Struct With Named Fields
A custom data type based on struct has a name and may have named fields, which are variable declarations with variable names and only their data types.
In the following codes, we have a
Person struct data type with two named fields –
last_name and
first_name, and we access them using the dot notation like in lines 13 and 14.
main.rs
Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
structPerson{
last_name:String,
first_name:String
}
fnmain(){
letperson:Person=Person{
first_name:String::from("Karl"),
last_name:String::from("San Gabriel")
};
println!("{}",person.first_name);
println!("{}",person.last_name);
}
Struct With Unnamed Fields
A struct can have a definition without specifying named fields, and it works like a Tuple. In this case, we only define the data types. To access each field, we need to use its ordinal value with the struct instance. Consider the codes below – the struct type
Coordinate has three unnamed fields. The first will have an ordinal value of 0, and the second will have an ordinal value of 1, and so on. Hence,
coordinate_instance.0 and
coordinate_instance.1 refer to the first and second fields, respectively.
There are two types of methods a struct can have – static and instance methods. A static method does not require an instance of the struct to call it, while an instance method needs an instance of a struct to invoke it.
main.rs
Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
structDog{
name:String
}
implDog{
// Instance method
fnget_name(&self)->String{
returnself.name.clone();
}
// Instance method
fnget_type(&self)->String{
returnString::from("Dog Struct");
}
// Static Method
fnget_struct_version()->String{
returnString::from("v1.0");
}
}
fnmain(){
// Static function
println!("{}",Dog::get_struct_version());
letdog:Dog=Dog{name:String::from("Bart")};
// Invoking an instance method
println!("{}",dog.get_name());
}
Struct With Methods And Trait
A trait can have zero or more instance methods without implementations for structs to implement.
Code Snippet
Rust
1
2
3
4
5
6
traitMyTrait{
fnmethod1(&self);
fnmethod2(&self);
fnmethod3(&self,param1:i32);
fnmethod4(&self,param1:String)->String;
}
Similar to structs, instance methods specify
&self as their first function parameter.
main.rs
Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
structDog{
name:String
}
implDog{
// Instance method
fnget_name(&self)->String{
returnself.name.clone();
}
// Instance method
fnget_type(&self)->String{
returnString::from("Dog Struct");
}
// Static Method
fnget_struct_version()->String{
returnString::from("v1.0");
}
}
traitPet{
fnbeg_for_food(&self);
}
implPetforDog{
fnbeg_for_food(&self){
println!("Beg!");
}
}
fnmain(){
// Static function
println!("{}",Dog::get_struct_version());
letdog:Dog=Dog{name:String::from("Bart")};
// Invoking an instance method
println!("{}",dog.get_name());
// Invoking an instance method
dog.beg_for_food();
}
To implement a trait for a struct, we use the
impl keyword and provide implementations for trait methods.
What if we implement a trait with only a static method? The method becomes part of the struct as if we defined it in the struct. Consider the code snippet below.