Rust generics
If we want to define two different classes, user and product, and we want to make sure that we cannot compare their ids directly, we might try
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Identifier<T> {
inner: u64,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct User {
id: Identifier<Self>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Product {
id: Identifier<Self>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn id_should_not_be_the_same() {
let user = User::default();
let product = Product::default();
assert_ne!(user.id, product.id);
assert_eq!(user.id.inner, product.id.inner);
}
}
but the code above does not work, since the generic T is never used.
To solve this, we need to use phantomdata as below
use std::marker::PhantomData;
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Identifier<T> {
inner: u64,
_tag: PhantomData<T>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct User {
id: Identifier<Self>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Product {
id: Identifier<Self>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn id_should_not_be_the_same() {
let user = User::default();
let product = Product::default();
// cannot directly compare the id
// assert_ne!(user.id, product.id);
assert_eq!(user.id.inner, product.id.inner);
}
}
The code below is useful when there are many common functions in Equation struct, only the logic of Iterator is different, and we need to implement 1st, 2nd, 3rd… equations, so we need to use generics
use std::marker::PhantomData;
#[derive(Debug, Default)]
pub struct Equation<IterMethod> {
current: u32,
_method: PhantomData<IterMethod>,
}
#[derive(Debug, Default)]
pub struct Linear;
#[derive(Debug, Default)]
pub struct Quadratic;
impl Iterator for Equation<Linear> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.current += 1;
if self.current >= u16::MAX as u32 {
return None;
}
Some(self.current)
}
}
impl Iterator for Equation<Quadratic> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.current += 1;
if self.current >= u32::MAX {
return None;
}
Some(self.current * self.current)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linear() {
let mut equation = Equation::<Linear>::default();
assert_eq!(Some(1), equation.next());
assert_eq!(Some(2), equation.next());
assert_eq!(Some(3), equation.next());
}
#[test]
fn test_quadratic() {
let mut equation = Equation::<Quadratic>::default();
assert_eq!(Some(1), equation.next());
assert_eq!(Some(4), equation.next());
assert_eq!(Some(9), equation.next());
}
}
A complicated generic function
`F: FnMut(i32) -> Iter` is a closure, accepts i32, return Iter
Iter is an Iterator, Item type is T
T implements Debug trait
pub fn consume_iterator<F, Iter, T>(mut f: F)
where
F: FnMut(i32) -> Iter,
Iter: Iterator<Item = T>,
T: std::fmt::Debug,
{
for item in f(10) {
println!("{:?}", item);
}
}
fn main() {
consume_iterator(|i| (0..i).into_iter())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_consume_iterator() {
consume_iterator(|i| (0..i).into_iter())
}
}