custom_types/
custom_types.rs

1//! This example shows how to interpolate over a non-standard type  
2//! Here we create a `NumericUnicode` type that allows us to interpolate across unicode code points.
3use lineic::LinearInterpolator;
4
5fn main() {
6    //
7    // An interpolator using the NumericUnicode type
8    let interpolator: LinearInterpolator<1, u8, NumericUnicode> =
9        LinearInterpolator::new(0..=26, &[['a'.into()], ['z'.into()]]);
10
11    //
12    // Perform an interpolation
13    let result = interpolator.interpolate(17);
14    println!(
15        "The 17th letter of the alphabet is: {:?}",
16        NumericUnicode::to_string(&result)
17    );
18}
19
20/// A type that allows iterating over unicode code points without hitting invalid code points.
21/// This is just an example type used to demo the custom types feature.
22#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
23struct NumericUnicode(u32);
24impl NumericUnicode {
25    /// Convert a slice of NumericUnicode values into a string
26    fn to_string(values: &[Self]) -> String {
27        values.iter().map(|v| char::from(*v)).collect()
28    }
29}
30impl From<char> for NumericUnicode {
31    fn from(value: char) -> NumericUnicode {
32        NumericUnicode(value as u32)
33    }
34}
35impl From<NumericUnicode> for char {
36    fn from(value: NumericUnicode) -> char {
37        // Is the inner value a valid unicode code point?
38        if let Some(c) = std::char::from_u32(value.0) {
39            c
40        } else {
41            // Pin to nearest valid unicode code point
42            let mut distance = 1;
43            loop {
44                let below = value.0.checked_sub(distance);
45                if let Some(c) = below.and_then(std::char::from_u32) {
46                    break c;
47                }
48
49                let above = value.0.checked_add(distance);
50                if let Some(c) = above.and_then(std::char::from_u32) {
51                    break c;
52                }
53
54                distance += 1;
55            }
56        }
57    }
58}
59
60impl std::fmt::Display for NumericUnicode {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "{}", char::from(*self))
63    }
64}
65
66// Implement the Numeric trait for NumericUnicode, so we can interpolate between them.
67impl lineic::Numeric for NumericUnicode {
68    // Some constants the interpolator will use
69    const MAX: Self = NumericUnicode(u32::MAX);
70    const ZERO: Self = NumericUnicode(0);
71    const ONE: Self = NumericUnicode(1);
72
73    // Get the absolute value of this number
74    fn abs(self) -> Self {
75        self
76    }
77
78    // Clamp this number between a minimum and maximum value
79    // Without panicking if max < min
80    fn clamp(self, min: Self, max: Self) -> Self {
81        Self(if min > max {
82            std::cmp::Ord::clamp(self.0, min.0, max.0)
83        } else {
84            std::cmp::Ord::clamp(self.0, max.0, min.0)
85        })
86    }
87
88    //
89    // These are methods instead of a trait requirement for better stdlib compatibility
90    //
91
92    fn from_usize(value: usize) -> Option<Self> {
93        u32::try_from(value).ok().map(Self)
94    }
95
96    fn into_f64(self) -> f64 {
97        self.0 as f64
98    }
99
100    fn from_f64(value: f64) -> Option<Self> {
101        if value < u32::MAX as f64 && value >= 0.0 {
102            Some(Self(value as u32))
103        } else {
104            None
105        }
106    }
107
108    //
109    // Checked arithmetic operations
110    //
111
112    fn checked_sub(self, other: Self) -> Option<Self> {
113        self.0.checked_sub(other.0).map(Self)
114    }
115
116    fn checked_add(self, other: Self) -> Option<Self> {
117        self.0.checked_add(other.0).map(Self)
118    }
119
120    fn checked_mul(self, other: Self) -> Option<Self> {
121        self.0.checked_mul(other.0).map(Self)
122    }
123
124    fn checked_div(self, other: Self) -> Option<Self> {
125        self.0.checked_div(other.0).map(Self)
126    }
127}