Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TryFrom<Time> for Duration #150

Closed
wants to merge 1 commit into from

Conversation

Aehmlo
Copy link
Contributor

@Aehmlo Aehmlo commented Jun 17, 2019

This PR changes @dunmatt's work from From<Time> to TryFrom<Time>. I don't see any other bad inputs that uom has to handle explicitly itself except for a negative value; let me know if there's some other error case to consider.

The glaringly obvious omission here is a version check: TryFrom/TryInto only exists on stable in Rust 1.35 and later, but the minimum version is 1.28. I don't see anywhere else in the crate that deals with this (though I could've sworn there was something), but I wanted to see how you'd like to handle that. It could easily be an opt-in feature, with the burden of version correctness falling on the user, but maybe we want to use something else? I'm not sure a build.rs would be great to introduce for just this, but I'm not really sure what else exists, so here's me soliciting ideas.

@iliekturtles
Copy link
Owner

I started reviewing (and making changes) yesterday and ran out of time. I pushed the changes to https://github.com/iliekturtles/uom/tree/Aehmlo-duration.

Bunch of formatting changes, TryFrom<Duration> added, tests converted to quickcheck!. The code doesn't compile currently as I was in the middle of changes. Let me know if the changes in that branch make sense or if you want me to write out a more formal review.

Right now I'm in agreement with you that adding a build.rs for rustc version checking goes a bit overboard and I'm leaning towards a feature. I haven't fully thought this through so I'm very open to arguments either way.

@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jun 19, 2019

After a brief reading of the code, I think the changes all make sense. I expect I'll have some time this week to look over it more fully and finish the thing up.

I'll use a feature for now and we can revisit it later if need be.

@Aehmlo Aehmlo mentioned this pull request Jun 19, 2019
@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jun 19, 2019

Something I just thought of: like temperature and temperature difference, there's a distinction between time and time interval (see std's Instant vs. Duration). Do you have any objections to the introduction of a "time interval" quantity and the migration of these conversions to that quantity?

@iliekturtles
Copy link
Owner

Aren't instant/duration higher level concepts? Similar to a point and a length vector in one dimensional space.

@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jun 20, 2019

I suppose that an instant is meaningless without some reference, so yeah, punting and letting consumers deal with this is probably best. It feels a little impure semantically, but since the omission of a TimeInterval quantity makes Time the semantically closest thing to a Duration, I'll leave the methods on there for now. Updated PR coming soon.

@Aehmlo Aehmlo force-pushed the duration branch 2 times, most recently from 0f5fa91 to 1242bbd Compare June 21, 2019 11:51
@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jun 21, 2019

Sorry, fighting with Working Copy. The real thing should be ready soon.

@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jun 22, 2019

Okay, it looks like I'm going to need to fix the commit history when I get back to my Mac, but the changes at Aehmlo/uom:duration are ready for review.

@iliekturtles
Copy link
Owner

iliekturtles commented Jun 24, 2019

Reviewed! Diff below of (most? all?) of the requested changed.

EDIT: Run clippy and cargo test. The patch has a couple import errors.

  • .travis.yml
    • Use try-from feature almost everywhere use_serde feature is used. Allows feature/edition check crates to re-use existing compiles and ensures feature gets tested.
  • Cargo.toml
    • Move try-from up with other public features in alphabetical order.
    • Change comment a little bit and wrap at 100 characters.
  • time.rs
    • I don't think a module is needed. It doesn't matter where the impl blocks are and I think the error enum should go right in the time module. This more closely matches how TryFrom errors are in the std library.
    • Added a number of blank lines and fixed some trailing whitespace.
    • Make doc comment locations consistent for TryFrom<T> impls. One is on the impl and one is on the fn. I added a "Add documentation" comment before I noticed the difference. Replace this if you apply the diff.
  • edition_check, feature_check/Cargo.toml
    • Added try-from feature so that these libraries re-use already-compiled uom libraries.
diff --git a/.travis.yml b/.travis.yml
index e6f1c72..9858d0d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,11 +22,11 @@ matrix:
     rust: stable
     script: |
       set -e
-      cargo test --all --verbose --features "use_serde"
-      cargo test --manifest-path tests/edition_check/Cargo.toml --verbose --features "use_serde"
+      cargo test --all --verbose --features "use_serde try-from"
+      cargo test --manifest-path tests/edition_check/Cargo.toml --verbose --features "use_serde try-from"
       cargo test --verbose --no-default-features --features "f32 si"
-      cargo test --verbose --no-default-features --features "autoconvert f32 si use_serde"
-      cargo test --verbose --no-run --no-default-features --features "autoconvert usize isize bigint bigrational si std use_serde"
+      cargo test --verbose --no-default-features --features "autoconvert f32 si use_serde try-from"
+      cargo test --verbose --no-run --no-default-features --features "autoconvert usize isize bigint bigrational si std use_serde try-from"
       cargo test --verbose --no-run --no-default-features --features "autoconvert usize u8 u16 u32 u64 isize i8 i16 i32 i64 bigint biguint rational rational32 rational64 bigrational f32 f64 std use_serde try-from"
   - name: Rustfmt
     rust: 1.35.0
diff --git a/Cargo.toml b/Cargo.toml
index 5c80a4c..b982bff 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -69,6 +69,10 @@ f32 = []
 f64 = []
 si = []
 std = ["num-traits/std"]
+# The `try-from` feature provides `impl TryFrom<Time> for Duration` and `impl TryFrom<Duration> for
+# Time`. The `TryFrom` trait was stabilized in Rust 1.34.0 and will cause uom to fail to compile if
+# enabled with an earlier version of Rust.
+try-from = []
 # The `use_serde` feature exists so that, in the future, other dependency features like num/serde
 # can be added. However, num/serde is currently left out because it has not yet been updated to
 # Serde 1.0. It is also necessary to name the feature something other than `serde` because of a
@@ -77,10 +81,6 @@ use_serde = ["serde"]
 # Internal features to include appropriate num-* crates.
 rational-support = ["num-rational"]
 bigint-support = ["num-bigint", "num-rational/bigint"]
-# The `try-from` feature currently provides `impl TryFrom<Time> for Duration` and 
-# `impl TryFrom<Duration> for Time`. These traits were stabilized in Rust 1.34.0, so this feature
-# will not work on earlier Rust versions.
-try-from = []
 
 [[example]]
 name = "base"
diff --git a/src/si/time.rs b/src/si/time.rs
index 6c00cd2..ab6aea7 100644
--- a/src/si/time.rs
+++ b/src/si/time.rs
@@ -1,5 +1,8 @@
 //! Time (base unit second, s).
 
+use lib::time::Duration;
+use num::{AsPrimitive, FromPrimitive, Zero};
+
 quantity! {
     /// Time (base unit second, s).
     quantity: Time; "time";
@@ -45,86 +48,83 @@ quantity! {
     }
 }
 
+/// An error encountered converting between `Time` and `Duration`.
+#[cfg(feature = "try-from")]
+#[derive(Debug, Clone, Copy)]
+pub enum TryFromError {
+    /// The given time interval was negative, making conversion to a duration nonsensical.
+    ///
+    /// To convert a negative time interval to a duration, first use `abs` to make it positive.
+    NegativeDuration,
+
+    /// The given time interval exceeded the maximum size of a `Duration`.
+    Overflow,
+}
 
-/// `Time` ⇌ `Duration` conversions.
+/// Attempt to convert the given `Time` to a `Duration`.
+///
+/// For possible failure modes, see [`TryFromError`][TryFromError].
+///
+/// ## Notes
+///
+/// The `Duration` to `Time` conversion is tested to be accurate to within 1 nanosecond (to allow
+/// for floating point rounding error). If greater precision is needed, consider using a different
+/// underlying storage type or avoiding the conversion altogether.
+///
+/// [TryFromError]: enum.TryFromError.html
 #[cfg(feature = "try-from")]
-pub mod convert {
-    use lib::time::Duration;
-    use num::{AsPrimitive, FromPrimitive, Zero};
-    use super::{nanosecond, second, Time};
-    
-    /// An error encountered in converting a `Time` to a `Duration`.
-    #[derive(Debug, Clone, Copy)]
-    pub enum TryFromError {
-        /// The given time interval was negative, making conversion to a duration nonsensical.
-        ///
-        /// To convert a negative time interval to a duration, first use `abs` to make it positive.
-        NegativeDuration,
-        /// The given time interval exceeded the maximum size of a `Duration`.
-        Overflow,
+impl<U, V> ::lib::convert::TryFrom<Time<U, V>> for Duration
+where
+    U: ::si::Units<V> + ?Sized,
+    V: ::num::Num + ::Conversion<V> + ::lib::cmp::PartialOrd + AsPrimitive<u64> + AsPrimitive<u32>,
+    second: ::Conversion<V, T = V::T>,
+    nanosecond: ::Conversion<V, T = V::T>,
+{
+    type Error = TryFromError;
+
+    fn try_from(time: Time<U, V>) -> Result<Self, Self::Error> {
+        if time < Time::<U, V>::zero() {
+            return Err(TryFromError::NegativeDuration);
+        }
+
+        let secs = time.get::<second>().as_();
+        let nanos = (time % Time::<U, V>::new::<second>(V::one())).get::<nanosecond>().as_();
+
+        Ok(Duration::new(secs, nanos))
     }
+}
+
+/// Add documentation.
+#[cfg(feature = "try-from")]
+impl<U, V> ::lib::convert::TryFrom<Duration> for Time<U, V>
+where
+    U: ::si::Units<V> + ?Sized,
+    V: ::num::Num + ::Conversion<V> + FromPrimitive,
+    second: ::Conversion<V, T = V::T>,
+    nanosecond: ::Conversion<V, T = V::T>,
+{
+    type Error = TryFromError;
 
-    /// Attempts to convert the given `Time` to a `Duration`.
+    /// Attempt to convert the given `Duration` to a `Time`.
     ///
     /// For possible failure modes, see [`TryFromError`][TryFromError].
     ///
     /// ## Notes
     ///
-    /// The `Duration` to `Time` conversion is tested to be accurate to within 1 nanosecond
-    /// (to allow for floating point rounding error). If greater precision is needed, consider
-    /// using a different backing storage type or avoiding the conversion altogether.
+    /// The `Duration` to `Time` conversion is tested to be accurate to within 100 nanoseconds (to
+    /// allow for floating point rounding error). If greater precision is needed, consider using a
+    /// different underlying storage type or avoiding the conversion altogether.
     ///
     /// [TryFromError]: enum.TryFromError.html
-    impl<U, V> ::lib::convert::TryFrom<Time<U, V>> for Duration
-    where
-        U: ::si::Units<V> + ?Sized,
-        V: ::num::Num + ::Conversion<V> + ::lib::cmp::PartialOrd + AsPrimitive<u64> + AsPrimitive<u32>,
-        second: ::Conversion<V, T = V::T>,
-        nanosecond: ::Conversion<V, T = V::T>,
-    {
-        type Error = TryFromError;
-    
-        fn try_from(time: Time<U, V>) -> Result<Self, Self::Error> {
-            if time < Time::<U, V>::zero() {
-                return Err(TryFromError::NegativeDuration);
-            }
-    
-            let secs = time.get::<second>().as_();
-            let nanos = (time % Time::<U, V>::new::<second>(V::one())).get::<nanosecond>().as_();
-    
-            Ok(Duration::new(secs, nanos))
-        }
-    }
-    
-    impl<U, V> ::lib::convert::TryFrom<Duration> for Time<U, V>
-    where
-        U: ::si::Units<V> + ?Sized,
-        V: ::num::Num + ::Conversion<V> + FromPrimitive,
-        second: ::Conversion<V, T = V::T>,
-        nanosecond: ::Conversion<V, T = V::T>,
-    {
-        type Error = TryFromError;
-        /// Attempts to convert the given `Duration` to a `Time`.
-        ///
-        /// For possible failure modes, see [`TryFromError`][TryFromError].
-        ///
-        /// ## Notes
-        ///
-        /// The `Duration` to `Time` conversion is tested to be accurate to within 100 nanoseconds
-        /// (to allow for floating point rounding error). If greater precision is needed, consider
-        /// using a different backing storage type or avoiding the conversion altogether.
-        ///
-        /// [TryFromError]: enum.TryFromError.html
-        fn try_from(duration: Duration) -> Result<Self, Self::Error> {
-            let secs = V::from_u64(duration.as_secs());
-            let nanos = V::from_u32(duration.subsec_micros());
-    
-            match (secs, nanos) {
-                (Some(secs), Some(nanos)) => {
-                    Ok(Time::<U, V>::new::<second>(secs) + Time::<U, V>::new::<nanosecond>(nanos))
-                },
-                _ => Err(TryFromError::Overflow),
+    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
+        let secs = V::from_u64(duration.as_secs());
+        let nanos = V::from_u32(duration.subsec_micros());
+
+        match (secs, nanos) {
+            (Some(secs), Some(nanos)) => {
+                Ok(Time::<U, V>::new::<second>(secs) + Time::<U, V>::new::<nanosecond>(nanos))
             }
+            _ => Err(TryFromError::Overflow),
         }
     }
 }
@@ -154,6 +154,7 @@ mod tests {
                 if *v < V::zero() {
                     return TestResult::discard();
                 }
+
                 test_try_into(Time::try_from(Duration::from_nanos(v.as_())), v)
             }
 
@@ -171,22 +172,26 @@ mod tests {
                 (Ok(t), true) => {
                     let d = Duration::from_nanos(v.as_());
                     let r = if d > t { d - t } else { t - d };
+
                     Duration::from_nanos(1) >= r
                 },
                 (Err(_), false) => true,
                 _ => false,
             };
+
             TestResult::from_bool(ok)
         }
-        
+
         fn test_try_into(t: Result<Time<V>, TryFromError>, v: A<V>) -> TestResult {
             let ok = if let Ok(t) = t {
                 let b = Time::new::<nanosecond>(v.as_());
                 let d = if t > b { t - b } else { b - t };
+
                 d <= Time::new::<nanosecond>(V::from_u8(100).unwrap())
             } else {
                 false
             };
+
             TestResult::from_bool(ok)
         }
     }
diff --git a/tests/edition_check/Cargo.toml b/tests/edition_check/Cargo.toml
index 81182b2..f043ba9 100644
--- a/tests/edition_check/Cargo.toml
+++ b/tests/edition_check/Cargo.toml
@@ -9,3 +9,4 @@ uom = { path = "../.." }
 [features]
 default = []
 use_serde = ["uom/use_serde"]
+try-from = ["uom/try-from"]
diff --git a/tests/feature_check/Cargo.toml b/tests/feature_check/Cargo.toml
index 803a6a5..e86934e 100644
--- a/tests/feature_check/Cargo.toml
+++ b/tests/feature_check/Cargo.toml
@@ -8,3 +8,4 @@ uom = { path = "../.." }
 [features]
 default = []
 use_serde = ["uom/use_serde"]
+try-from = ["uom/try-from"]

@iliekturtles
Copy link
Owner

  • lib.rs and README.md need doc updates for the new feature.

@iliekturtles
Copy link
Owner

@Aehmlo, how are these changes coming?

@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jul 8, 2019

I’ve had a busy couple of weeks, but I anticipate getting this wrapped up this week or maybe over the coming weekend.

@iliekturtles
Copy link
Owner

Great! I'm coming off a busy long weekend myself.

@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jul 16, 2019

Okay, this week for sure. :)

@Aehmlo
Copy link
Contributor Author

Aehmlo commented Jul 16, 2019

I started to take out mod convert, but then realized that the conditional doc comments (#[cfg_attr(feature = "try-from", doc = "…")) are very fragile and difficult to work with for comments this large. It's also much easier to only have two #[cfg(feature = "try-from")]s in the whole file (one on the module, one on the tests) than to ensure that they are put on every impl/enum/etc. If you feel strongly about having the structure be flat like std, I might suggest that we just reexport the module contents. That way we get to keep the ergonomics of a granular feature switch and easy doc comments, but it's not exposed weirdly.

@iliekturtles
Copy link
Owner

Go ahead and leave the mod for the moment.

@iliekturtles
Copy link
Owner

@Aehmlo if you want to push the changes you've been able to make so far I hope to have time over the weekend to review the progress. Don't worry if it's not complete.

iliekturtles pushed a commit that referenced this pull request Sep 4, 2019
iliekturtles pushed a commit that referenced this pull request Sep 8, 2019
@iliekturtles
Copy link
Owner

I made the changes from the last review and found out there are errors for BigInt. I'll look at switching from AsPrimitive to ToPrimitive and handling conversion errors as TryFromError::Overflow

iliekturtles pushed a commit that referenced this pull request Sep 9, 2019
iliekturtles pushed a commit that referenced this pull request Sep 9, 2019
iliekturtles pushed a commit that referenced this pull request Sep 10, 2019
iliekturtles pushed a commit that referenced this pull request Sep 13, 2019
Functionality is not implemented for `Ratio` types because ToPrimitive
is not implemented. Resolves #131 and #150.
@iliekturtles
Copy link
Owner

@Aehmlo I finally have a build that passes. I ended up needing to remove tests for Ratio since it implements neither AsPrimitive or ToPrimitive. Please review d8e8306 if you get a chance. I'll planning on merging this weekend or early next week if nothing comes up.

@Aehmlo
Copy link
Contributor Author

Aehmlo commented Sep 13, 2019

Apologies for basically disappearing; I’ll take a look over the weekend.

iliekturtles pushed a commit that referenced this pull request Sep 18, 2019
@iliekturtles
Copy link
Owner

Manually marking complete. Github only completed #131 even though both were referenced in the commit message.

iliekturtles pushed a commit that referenced this pull request Sep 18, 2019
Functionality is not implemented for `Ratio` types because ToPrimitive
is not implemented. Resolves #131 and #150.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants