1

Hi Rustaceans of Stack Overflow, I am continuing my new project in Rust and I am still having trouble around the clock trait that I have been trying to build. For reference, I went with the second solution (the one recommended to me).

My challenge now is that I am finding that the need to specify a generic time zone is "bleeding" out into other structs. Essentially, if any struct needs to contain a clock (or any other struct that contains a clock), a generic is required on that struct also, even though these structs have little to do with time or time zones.

// Assert for purposes of argument that this struct is not really about time
// or time zones and it is confusing why one is required.
pub struct <Tz: TimeZone> UnrelatedThing {
  // Other stuff...

  clock: &dyn Clock<Tz>,
}

I ultimately want to get to a point where I can store a time zone as a property of something and be able to have a method that returns DateTime objects in that time zone:

struct TimeRelatedThing {
  tz: dyn TimeZone,
}

impl TimeRelatedThing {
  pub fn now(&self) -> DateTime<??> {
    Utc::now().with_timezone(&self.tz)
  }
}

The piece I can not get to work correctly is the DateTime<??> part, because the time zone is not necessarily known at compile time. What is the right way to approach this? It seems like I can do Box<dyn impl Datelike + Timelike> but that seems to my naive understanding like high overhead? It certainly seems like a lot of work for people who want to write code against my function.

I should mention that I am new to Rust, and it is entirely possible I am misunderstanding something elementary, but I have looked at the documentation to try to understand better and have not found what I was looking for.

UPDATE

After more iterating, I have a state where I always ask for the timezone as an argument:

use chrono::{DateTime, TimeZone, Utc};

/// A trait representing the internal clock for timekeeping requirements.
/// Note that for some testing environments, clocks may be stubs.
pub trait Clock {
  /// Return the current datetime.
  fn now<Tz: TimeZone>(&self, tz: Tz) -> DateTime<Tz>;
}

/// A clock that reliably reports system time in the requested time zone.
pub struct SystemClock {}
impl SystemClock {
  /// Return a new system clock in the given time zone.
  pub fn new() -> SystemClock {
    SystemClock {}
  }
}
impl Clock for SystemClock {
  /// Return the current datetime.
  fn now<Tz: TimeZone>(&self, tz: Tz) -> DateTime<Tz> {
    Utc::now().with_timezone(&tz)
  }
}

/// A stub clock that reports "stub time", useful for replaying historical
/// data and other purposes.
pub struct StubClock {
  /// The current "now" time, in UTC.
  cursor: DateTime<Utc>,
}
impl StubClock {
  /// Return a new stub clock in the given time zone, and with the given
  /// starting time.
  pub fn new<Tz: TimeZone>(start: DateTime<Tz>) -> StubClock {
    StubClock { cursor: start.with_timezone(&Utc) }
  }

  /// Set the cursor to the current time.
  pub fn set_cursor<Tz: TimeZone>(&mut self, cursor: DateTime<Tz>) -> &Self {
    self.cursor = cursor.with_timezone(&Utc);
    return self;
  }
}
impl Clock for StubClock {
  /// Return the current datetime.
  fn now<Tz: TimeZone>(&self, tz: Tz) -> DateTime<Tz> {
    self.cursor.clone().with_timezone(&tz)
  }
}

This still seems slightly less good than having a time zone as the property of my clock, since I expect a clock to consistently operate in a single time zone, although there are indisputably advantages.

This creates a new problem, though, which is that now Clock is not an "object-safe" trait, because TimeZone seems to have Sized as a supertrait. :-(

Luke Sneeringer
  • 9,270
  • 2
  • 35
  • 32
  • I think you have the right understanding that there are 3 options: let the generic timezone bleed, allow the slight overhead of dynamic dispatch, or just choose a particular struct for `Clock` and stick with it. That's just the way it has to work, or else how would a generic struct inside a non-generic struct know what type to use? As for object safety, again I think you have the right understanding, there's no simple way to fix the error. – Coder-256 Sep 01 '21 at 00:26

0 Answers0