Rust Trait
Rust Trait
- A Rust language feature known as a “rust trait” outlines the capabilities of each type that it can offer.
- A trait is comparable to an interface feature specified in other languages.
- A characteristic is a mechanism to specify a set of behaviors by organizing the method signatures into groups.
- The trait keyword is used to define a trait.
Syntax
trait trait_name
//body of the trait.
In the example above, we first define the trait and then its name. The behavior of a type that implements the trait is described by the method signature, which is stated inside the curly brackets.
Example:
struct Triangle
{
base : f64,
height : f64,
}
trait HasArea
{
fn area(&self)->f64;
}
impl HasArea for Triangle
{
fn area(&self)->f64
{
0.5*(self.base*self.height)
}
}
fn main()
{
let a = Triangle{base:10.5,height:17.4};
let triangle_area = a.area();
println!("Area of a triangle is {}",triangle_area);
}
Output:
Area of a triangle is 91.35
The area() function is declared in the trait HasArea, which is defined in the example above. The type Triangle uses the HasArea implementation. All that is required to invoke the area() function is to use the structure’s instance, a.area().
Trait as Arguments
Additionally, traits can be used as a variety of arguments.
The area() function is defined in the sample above, which also implements the HasArea trait. The area() function is called using an instance of the type that implements the HasArea trait, and we may write the calculate_area() function that calls the area() function.
Syntax:
fn calculate_area(item : impl HasArea)
println!("Area of the triangle is : {}",item.area());
}
Trait bounds on Generic functions
Because they characterize the behavior of various approaches, traits are helpful. However, generic functions are exempt from this restriction.
understand this through a simple scenario:
fn calculate_area( item : T)
println!(?Area of a triangle is {}?, item.area());
The Rust compiler throws a “error that no method named found of type T” in the example above. The following mistake can be fixed if we bind the trait to the generic T:
fn calculate_area (item : T)
{
println!("Area of a triangle is {} ",item.area());
}
As seen above, “T can be of any type that implements HasArea trait” is indicated by the notation <T: HasArea>. Any type that implements the HasArea trait will have an area() function, the Rust compiler learned.
Example:
trait HasArea
{
fn area(&self)->f64;
}
struct Triangle
{
base : f64,
height : f64,
}
impl HasArea for Triangle
{
fn area(&self)->f64
{
0.5*(self.base*self.height)
}
}
struct Square
{
side : f64,
}
impl HasArea for Square
{
fn area(&self)->f64
{
self.side*self.side
}
}
fn calculate_area(item : T)
{
println!("Area is : {}",item.area());
}
fn main()
{
let a = Triangle{base:10.5,height:17.4};
let b = Square{side : 4.5};
calculate_area(a);
calculate_area(b);
}
Output:
Area is : 91.35
Area is : 20.25
In the above example, calculate_area() function is generic over “T”.
Rules for implementing traits
The trait’s implementation is subject to two limitations:
- The trait cannot be used to any kind of data if it is not declared in your scope.
Example:
use::std::fs::File;
fn main()
{
let mut f = File::create("hello.txt");
let str = "javaTpoint";
let result = f.write(str);
}
Output:
error : no method named 'write' found.
let result = f.write(str);
In the above case, Rust compiler throws an error, i.e., “no method named ‘write’ found” as Utilize::std::fs::The write() method is not present in the file; namespace. Consequently, we must
use the Write trait to remove the compilation error.
We must define the trait that we are putting into practice. As an illustration, once the HasArea trait is defined, the type i32 can use this feature. Unfortunately, because the type and trait are not declared in our crate, we were unable to implement the toString trait that Rust defines for the type i32.
Multiple trait bounds
- Using ‘+’ operator.
Use the + operator to bind additional traits if desired.
Example:
use std::fmt::{Debug, Display};
fn compare_prints(t: &T)
{
println!("Debug: '{:?}'", t);
println!("Display: '{}'", t);
}
fn main() {
let string = "javaTpoint";
compare_prints(&string);
}
Output:
Debug: ' "javaTpoint"'
Display: ' javaTpoint'
The ‘+’ operator is used in the example above to limit the Display and Debug traits to the type ‘T’.
Using ‘where’ clause.
- A ‘where’ clause, which comes before the starting bracket ‘{‘, can be used to write a bound.
- This ‘where’ clause can also be used with arbitrary kinds.
- When the ‘where’ clause is employed, the grammar becomes more expressive than it would otherwise be.
Let’s look:
fn fun(t:T,v:V)->i32
//block of code;
When ‘where’ is used in the above case:
fn fun(t:T, v:V)->i32
where T : Display+ Debug,
V : Clone+ Debug
//block of code;
The program is more understandable and expressive in the second of the aforementioned circumstances where the ‘where’ clause is utilized.
Example:
trait Perimeter
{
fn a(&self)->f64;
}
struct Square
{
side : f64,
}
impl Perimeter for Square
{
fn a(&self)->f64
{
4.0*self.side
}
}
struct Rectangle
{
length : f64,
breadth : f64,
}
impl Perimeter for Rectangle
{
fn a(&self)->f64
{
2.0*(self.length+self.breadth)
}
}
fn print_perimeter(s:Square,r:Rectangle)
where Square : Perimeter,
Rectangle : Perimeter
{
let r1 = s.a();
let r2 = r.a();
println!("Perimeter of a square is {}",r1);
println!("Perimeter of a rectangle is {}",r2);
}
fn main()
{
let sq = Square{side : 6.2};
let rect = Rectangle{length : 3.2,breadth:5.6};
print_perimeter(sq,rect);
}
Output:
Perimeter of a square is 24.8
Perimeter of a rectangle is 17.6
Default methods
If the definition of a method is already known, it is possible to add a default method to the trait declaration.
Let’s look:
trait Sample
fn a(&self);
fn b(&self)
{
println!("Print b");
}
The default behavior is added to the trait specification in the aforementioned scenario. It’s also possible to change the default behavior.
Example:
trait Sample
{
fn a(&self);
fn b(&self)
{
println!("Print b");
}
}
struct Example
{
a:i32,
b:i32,
}
impl Sample for Example
{
fn a(&self)
{
println!("Value of a is {}",self.a);
}
fn b(&self)
{
println!("Value of b is {}",self.b);
}
}
fn main()
{
let r = Example{a:5,b:7};
r.a();
r.b();
}
Output:
Value of a is : 5
Value of b is : 7
The b() function’s behavior is defined in the trait that is overridden in the example above. We can therefore draw the conclusion that we have the ability to override the trait’s stated method.
Inheritance
Inheritance refers to a trait that is derived from another trait. There are situations where adopting one trait necessitates implementing another. ‘B’ trait can be derived from ‘A’ trait as follows:
trait B : A;
Example:
trait A
{
fn f(&self);
}
trait B : A
{
fn t(&self);
}
struct Example
{
first : String,
second : String,
}
impl A for Example
{
fn f(&self)
{
print!("{} ",self.first);
}
}
impl B for Example
{
fn t(&self)
{
print!("{}",self.second);
}
}
fn main()
{
let s = Example{first:String::from("javaTpoint"),second:String::from("tutorial")};
s.f();
s.t();
}
Output:
javaTpoint tutorial
The ‘B’ trait is being implemented by our program in the example above. Thus, the ‘A’ trait must also be implemented. Rust generates an error if our application doesn’t implement the ‘A’ trait.