Traits:
We presented a lot of introductory material for Traits in the
Generics and Traits Bite. If you haven't
looked at that yet, now is a good time to do so.
In the example, below, we extend the Point<T> type with traits SpaceTime<T>
and Display. SpaceTime<T> declares an interface for accessing 3-dimensional space
coordinates and date time information from a Point<T> or any other type that
implements the trait.
Here's the SpaceTime<T> definition:
SpaceTime<T>
pub trait SpaceTime<T> {
fn get_coordinates(&self) -> [T; 3];
fn set_coordinates(&mut self, coor: &[T; 3]);
fn set_time(&mut self, st: DateTime<Local>);
fn get_time(&self) -> DateTime<Local>;
fn get_time_string(&self) -> String;
}
A Point<T> that implements this trait could be used to annotate
data from an astronomical observatory. A system for such annotation would probably
use several different types of points to mark different types of observables. The
trait allows any of those point types to make their space and time information
available to mapping and display functions.
That works because the functions accept a SpaceTime<T> trait object rather
than a specific point type. So traits allow us to build very flexible software
based on use rather than implementation details.
Example: Point<T> with SpaceTime & Display Traits
point::lib.rs
/////////////////////////////////////////////////////////////
// point::lib.rs - Demo Traits //
// //
// Jim Fawcett, https://JimFawcett.github.io, 24 Jun 2020 //
/////////////////////////////////////////////////////////////
/*
Point represents points in 3-dimensional space and time.
Example hypothetical applications:
- Aircraft flight navigation systems
- Airport and Marine port area management systems
- Autonomous vehicle control
- Drawing applications
Point implements a SpaceTime trait. That allows a
function to use space and time information from any
object that implements the SpaceTime<T>trait.
For example, in an Aircraft, points might be used
as fundamental data structure for capturing instrument
information, navigation computations, and in flight
recording.
Each of these applications might use different internal
data, e.g., ids, other information, ... but a function
that accepts a SpaceTime trait object can extract that
information from any of them.
*/
use chrono::offset::Local;
use chrono::DateTime;
use chrono::{Datelike, Timelike};
use std::fmt::*;
/*-----------------------------------------------------------
declare SpaceTime<T> trait
- note that this trait is generic
*/
pub trait SpaceTime<T> {
fn get_coordinates(&self) -> [T; 3];
fn set_coordinates(&mut self, coor: &[T; 3]);
fn set_time(&mut self, st: DateTime<Local>);
fn get_time(&self) -> DateTime<Local>;
fn get_time_string(&self) -> String;
}
/*-- define Point<T> type --*/
#[derive(Debug)]
pub struct Point<T>
where T:Default + Debug {
x:T, y:T, z:T, t:DateTime<Local>, n:String,
}
/*-- implement Time trait --*/
impl<T> SpaceTime<T> for Point<T>
where T:Default + Debug + Clone {
fn set_time(&mut self, st: DateTime<Local>) {
self.t = st;
}
fn get_time(&self) -> DateTime<Local> {
self.t
}
fn get_time_string(&self) -> String {
let year = self.t.year().to_string();
let mon = self.t.month().to_string();
let day = self.t.day().to_string();
let hour = self.t.hour().to_string();
let min = self.t.minute().to_string();
let sec = self.t.second().to_string();
let dt = format!(
"{}::{:0>2}::{:0>2} {:0>2}::{:0>2}::{:0>2}",
year, mon, day, hour, min, sec
);
dt
}
/*-- set coordinates from array slice --*/
fn set_coordinates(&mut self, coor: &[T; 3]) {
self.x = coor[0].clone();
self.y = coor[1].clone();
self.z = coor[2].clone();
}
/*-- return array of three spatial coordinates --*/
fn get_coordinates(&self) -> [T; 3] {
[self.x.clone(), self.y.clone(), self.z.clone()]
}
/*-- time is returned with Time::get_time() --*/
}
/*-- implement Display trait --*/
impl<T> Display for Point<T>
where T:Default + Debug + Clone {
fn fmt(&self, f: &mut std::fmt::Formatter)
-> std::fmt::Result {
write!(
f, "{{ {:?}, {:?}, {:?}, {}, {:?} }}",
&self.x, &self.y, &self.z,
&self.get_time_string(), &self.n
)
}
}
/*-- implement Point methods --*/
impl<T> Point<T> where T:Default + Debug + Clone {
pub fn new() -> Point<T> {
Point {
x:std::default::Default::default(),
y:std::default::Default::default(),
z:std::default::Default::default(),
t:Local::now(),
n:String::default()
}
}
pub fn default() -> Point<T> {
Point::new()
}
pub fn set_name(&mut self, name: &str) {
self.n = name.to_string();
}
pub fn get_name(&self) -> &str {
&self.n
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn construct () {
let pt = Point::<i32>::new();
assert_eq!(pt.get_coordinates(), [0, 0, 0]);
}
#[test]
fn set_coor() {
let mut pt = Point::<i32>::new();
pt.set_coordinates(&[1, 2, 3]);
assert_eq!(pt.get_coordinates(), [1, 2, 3]);
}
#[test]
fn get_time() {
use chrono::{Duration};
let two_sec = Duration::seconds(2);
let ts_start = Local::now() - two_sec;
let pt = Point::<i32>::new();
let ts_now = pt.get_time();
let ts_stop = Local::now() + two_sec;
assert!(ts_start < ts_now);
assert!(ts_now < ts_stop);
}
}
Using Code - point::test1.rs
/////////////////////////////////////////////////////////////
// point::test1.rs - Demo Point<T> with SpaceTime and //
// Display Traits //
// //
// Jim Fawcett, https://JimFawcett.github.io, 24 Jun 2020 //
/////////////////////////////////////////////////////////////
#![allow(dead_code)]
use point::{*};
use point::SpaceTime;
use chrono::{Local};
use std::fmt::*;
/*-----------------------------------------------------------
function accepting SpaceTime<T> trait object
- Does not depend on a specific type for pt, just the
SpaceTime<T> trait - like an interface.
- Can't use name. That's not part of trait.
*/
fn use_space_time_info<T:Debug>(
pt : &dyn SpaceTime<T>
) {
print!("\n -- using SpaceTime trait object --");
print!(
"\n -- display coords & time w/ Display format --"
);
print!(
"\n coordinates are: {:?}", &pt.get_coordinates()
);
print!("\n point time is: {}", &pt.get_time_string());
}
/*-----------------------------------------------
Exercise Point<i32> and Point<f64>
funtionality
*/
fn main() {
print!("\n Demonstrating Point Type");
print!("\n ==========================");
print!(
"\n -- create Point<i32> "
"& display w/ Display format --"
);
let mut pt = Point::<i32>::new();
pt.set_coordinates(&[1, 2, 3]);
pt.set_time(Local::now());
pt.set_name("pt");
print!("\n pt = {}", &pt);
println!();
use_space_time_info(&pt);
println!();
print!(
"\n -- mutate coordinates & display again --"
);
pt.set_coordinates(&[3, 2, 1]);
pt.set_name("mutated pt");
print!("\n pt = {}", pt);
print!(
"\n coordinates are: {:?}",
pt.get_coordinates()
);
println!();
print!(
"\n -- display point with Debug format --"
);
print!("\n pt in Debug format: {:?}", pt);
println!();
print!("\n -- creating Point<f64> --");
let mut ptd = Point::<f64>::new();
ptd.set_coordinates(&[0.5, -0.5, 1.75]);
ptd.set_name("ptd");
print!("\n ptd = {}", ptd);
use_space_time_info(&ptd);
print!("\n\n That's all Folks!\n\n");
}
Output:
Demonstrating Point Type
==========================
-- create Point and display with Display format --
pt = { 1, 2, 3, 2020::06::25 16::55::36, "pt" }
-- using SpaceTime trait object --
-- display coordinates and time with Display format --
coordinates are: [1, 2, 3]
point time is: 2020::06::25 16::55::36
-- mutate coordinates and display again --
pt = {
3, 2, 1, 2020::06::25 16::55::36, "mutated pt"
}
coordinates are: [3, 2, 1]
-- display point with Debug format --
pt in Debug format: Point {
x: 3, y: 2, z: 1,
t: 2020-06-25T16:55:36.838484600-04:00,
n: "mutated pt" }
-- creating Point<f64> --
ptd = {
0.5, -0.5, 1.75, 2020::06::25 16::55::36, "ptd"
}
-- using SpaceTime trait object --
-- display coordinates and time with Display format --
coordinates are: [0.5, -0.5, 1.75]
point time is: 2020::06::25 16::55::36
That's all Folks!