generics in rusts allow us to have stand-in types for our concrete types. Generics allows us to create code that can operate on many different types. They are abstract stand-ins for concrete types. generics are similar to Generic Interfaces
in typescript
#[derive(Debug)]
struct Point<T, U> {
x: T,
y: U,
}
let point_int = Point::<i32, i32> { x: 5, y: 8 };
let point_str = Point::<i32, &str> { x: 10, y: "john" };
traits allows us to represent a capability that can be implemented in many different types.
traits represents a capability of what a type can do and can be shared with other types. we can use it to create shared behavior in an abstract way. traits
are similar to abi
in solidity, interfaces
in typescript, classes
in dart.
trait Overview {
fn overview(&self) -> String {
String::from("this is me learning rust")
}
}
impl Overview for Course {
fn overview(&self) -> String {
format!("{} {}", self.author, self.title)
}
}
let course1 = Course {
title: String::from("rust"),
author: String::from("peter anyaogu"),
};
println!("{}", course1.overview());
traits can also be passed into functions, as parameters. you would be able to call specific methods implemented in the trait.
fn call_with_trait<T: Overview>(item: &T) {
println!("overview called: {}", item.overview());
}
call_with_trait(course1);
simply put, drop frees the memory the associated with variable using it. example when a variable goes out of scope, or moving elements in a vector etc. rust handle this internally. but it can be done manually. eg.
impl Drop for Course {
fn drop(&mut self) {
println!("dropping: {}", self.author);
}
}
drop(course1);
//dropping: peter anyaogu
// without calling drop, the text will still be logged, as it is dropped internally
clone traits are for types that can make copies of themselves. for it to work, it needs to construct an independent copy using the self keyword and return it. It simply means allocating copies of anything owned. and clones are expensive to run as well
trait Clone:Sized {
fn clone(&self) -> self;
fn clone_from(&mut self, source: &self){
*self = source.clone()
}
}
operator overloading allows us to make our own types support arithmetic
use std::ops::Add;
impl<T, U> Add for Point<T, U>
where
T: Add<Output = T>,
U: Add<Output = U>,
{
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Point {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
let coord = Point { x: 3.0, y: 5.0 };
let coord2 = Point { x: 1.0, y: 2.0 };
let sum = coord + coord2;
println!("sum of coordinates are: {:?}", sum);
// sum of coordinates are: Point { x: 4.0, y: 7.0 }