Git Product home page Git Product logo

dimensioned's Introduction

crates.io Build Status

Documentation

Dimensioned

A Rust library for compile-time dimensional analysis.

Its goal is to provide zero cost unit safety while requiring minimal effort from the programmer.

Use

Dimensioned requires at least Rust version 1.23.0 (and is tested on this version), although some features may require a newer version.

It does not depend on std; simple include without the default feature std. Doing so requires a nightly version of rustc.

If you are using Rust nightly, then you may enable the "oibit" feature of dimensioned. This will make it work a bit better for wrapping non-primitives in units. The recommended way to use dimensioned is by wrapping only primitives in units, in which case this feature is not helpful.

Contributing

Contributions are welcome! There aren't super strict contributing guidelines, but I have a few requests.

  • Note changes that you make in CHANGELOG.md.
  • Add documentation and tests for anything that you add.
  • Try to keep lists alphabetized in files not addressed by rustfmt.
  • Don't hesitate to prod me if I haven't responded to you in a timely manner. I get busy and forgetful, but I would like to repond to issues and PRs promptly.
  • Feel free to ask questions!

Examples

The Simulation Example provides a simple physics simulation and covers how one can use dimensioned with it in a couple different ways, and what the trade-offs are. If you're curious about what might be involved in adding dimensioned to one of your projects, or what it might look like in semi-real code, then that is the place for you.

The Conversion Example covers how one might implement conversions between unit systems.

Finally, just to get the juices flowing, here's a simple example illustrating some of what dimensioned can do:

extern crate dimensioned as dim;

use dim::{si, cgs};

// Calculates speed given a distance and time. Only works for SI units.
fn speed(dist: si::Meter<f64>, time: si::Second<f64>) -> si::MeterPerSecond<f64> {
    dist / time
}

use std::ops::Div;
use dim::dimensions::{Length, Time};
use dim::typenum::Quot;

// Calculates speed as before, but now we can use *any* unit system.
fn generic_speed<L, T>(dist: L, time: T) -> Quot<L, T>
    where L: Length + Div<T>, T: Time,
{
    dist / time
}

fn main() {
    let si_x = 6.0 * si::M;
    let si_t = 3.0 * si::S;
    let si_v = 2.0 * si::M / si::S;

    let si_v2 = speed(si_x, si_t);
    assert_eq!(si_v, si_v2);

    let cgs_x = 6.0 * cgs::M;
    let cgs_t = 3.0 * cgs::S;
    let cgs_v = 2.0 * cgs::M / cgs::S;

    let cgs_v2 = generic_speed(cgs_x, cgs_t);
    assert_eq!(cgs_v, cgs_v2);

    let si_v3 = cgs_v2.into();
    assert_eq!(si_v2, si_v3);
}

This example is also included as examples/readme-example.rs.

Unit Systems

Dimensioned aims to include unit systems for a large variety of uses. It also includes a make_units! macro to allow you to create any unit system you desire.

Error Messages

Probably the biggest weakness of dimensioned are the error messages generated. The type signatures coming from dimensioned tend to just look like a bunch of gobbly-guck. Someday, we may have a better way to display them.

For now, my advice is that when you get an error message involving dimensioned, just go to the line number and hopefully the issue will be apparant from the code alone.

Friends of dimensioned

If there are any libraries that work particularly well with dimensioned, such as the vector3d library used in part 3 of the simulation example, please let me know and they will be listed here.

dimensioned's People

Contributors

adeschamps avatar amelia10007 avatar droundy avatar ia0 avatar iliekturtles avatar jswrenn avatar llogiq avatar paholg avatar quentinmit avatar regexident avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dimensioned's Issues

Ergonomics

I would like using dimensioned to be as painless as possible. It should feel like you're working with primitives, only they yell at you more when you make mistakes.

If you have any thoughts on the ergonomics of dimensioned, in regards to anything, please add them here!

type aliases

Currently, we have aliases that look like this:

pub type Meter = SI<P1, Z0, Z0, Z0, Z0, Z0, Z0>;

which requires you to use it like this:

fn length() -> Dim<Meter, f64> {
    5.7 * m
}

I would like to switch the aliases to look like this:

pub type Meter<V> = Dim<SI<P1, Z0, Z0, Z0, Z0, Z0, Z0>, V>;

so that the above function could be replaced with:

fn lenth() -> Meter<f64> {
    5.7 * m
}

I am not certain that this won't cause issues in other places (especially in defining derived units, which I'm currently working on as well), but barring that I'm not 100% sure it's a good idea.

It makes dimensioned types cleaner to view and easier to type.

It makes error messages more cryptic (they won't change, but they contain Dim which users will no longer need to deal with directly).

It also might make some things more confusing. I'm not sure exactly.

derived units

It is important for both unit systems and users of them to be able to conveniently define derived units. Right now, you can do

type Newton = SI<P1, P1, N2>;

but that isn't great. I have a macro in progress that can turn

type Newton = derived!(Meter * Kilogram / Second / Second);

into

type Netwon = Quot<Quot<Prod<Meter, Kilogram>, Second>, Second>;

but that has flaws as well, mainly due to the typechecker not doing what we want. Basically, Newton won't always act like it's SI<P1, P1, N2>, but instead will be treated as the associated type Output of those computations, even though the latter, once computed, is the former. Basically, this issue.

One solution is to use a build script to generate derived types, but that doesn't help users make their own, which should be as painless as possible. Possibly this can be solved with syntax extensions, but I have no familiarity there.

Explicit explanation of how to unwrap values (eg for serialization)

There's a value_unsafe parameter, but it's unclear to me if that's the "right" way to do things if you just want to extract the value (sans the units). It'd be nice if this were explicitly stated somewhere, too, so that new users can discover it without asking.

Thanks!

Dimension constraint incompatible with derived types working properly.

If we have pub struct Dim<D: Dimension, V>(pub V, pub PhantomData<D>); then we can't do something like type MPS = Quot<Meter, Second>; and have it work properly.

So for now, we will declare Dim with pub struct Dim<D, V>(pub V, pub PhantomData<D>); (and also remove the constraint elsewhere where necessary).

Essentially, once this compiles, we can re-add the constraint (which will, unfortunately, be a breaking change).

has_units() -> bool

I can use your (paholg) suggestion to divide by 1 using the expected units, then visually inspect the formatted String to see if any mks values are present. If they are, then the units were unexpected and I know I have a problem with my math. Thanks dimensioned!

But... I believe this doesn't scale. Is it possible to get a public method to determine if the Dimensioned value is unitless?
Then I wouldn't have to visually inspect things to make sure only my expected type is there.

Alternatively I could format the Dimensioned value to a string and then parse the string to see if other units are there.

I'm just nervous about having to perform a mental visual check on all results.

Thoughts?

Key word for dimensionally unsafe actions

I would like to ensure that any interface that lets you take actions where units are not checked is clearly labeled.

For example, in v0.5 of dimensioned, there is a map function that lets you apply a function to the underlying value. In the rewrite, I am looking at having two functions, map and map_unsafe. Both do the same thing, but the new map is only defined when dealing with dimensionless quantities so you have no units to mess up.

I would like direct exposure to the underlying value to follow a similar naming convention, but the word "unsafe" has pretty clear memory-safety connotations in the Rust ecosystem and I would like to avoid it.

Thoughts?

Constant bikeshedding

Right now, a constant is defined for each unit we create. They tend to have names like m for 1 meter.

There's a couple things I don't like about this.

First, it goes against Rust's naming convention that constants should be uppercase -- but I think I like making these constants uppercase even less.

Second, it requires us to choose a "blessed" primitive when creating a unit system. Right now it's been f64, but what if you want f32 or something else? Well then you have to make your own anyway.

Recent changes to dimensioned mean that instead of Dim<Meter, f64> you write Meter<f64>, which means it's much cleaner to construct units without the consts.

The old way:

let x: Dim<Meter, f64> = Dim::new(3.4);

The new way:

let x = Meter::new(3.4);

This increase in constructor ergonomics makes want to get rid of pre-defining constants, but I figured I'd post this issue first in case anyone else has thoughts on the matter.

Support f256 via the qd github repo

Hello,

I'd like to investigate dimensioned, but I need f256 support for the quantum mechanics work I'd like to do.
Planck's constant is 6.62607015 × 10−34 so even f128 isn't good enough.

If you give me a few pointers I could take a stab at adding f256 support to dimensioned.

Thanks!

Angles (Radians/Degrees)

Should these be in a separate crate? I would like to derive the Radians unit from SI (m * m^-1), and have a conversion to/from Degrees. Then also make trigonometry functions with the correct units, as I do in cgmath::angle.

fmt String as Joules instead of MKS

I have a function like: fn() -> Joules.
However, when I print the Joules value I get something like: 5 m^2kgs^-2
Is there an easy way to print '5 Joules' instead?

If not, what do you think a good solution to this would be? (I'm not seeing an easy way to get access to the units to compare the set of units against 'Joule' etc.)
I'd be happy to take a crack at it.
Thanks!

How we define roots

Currently, when creating a unit system, you can express what power to which you want each base unit raised. For most unit systems, this will be 1, but for example, for cgs it's 2 for the centimeters and grams.

This has some nice and some not so nice properties. Before we get to them, let me list the alternatives:

  1. Add rational numbers to typenum. This is almost certainly more work that I want to do right now.
  2. Use, for example, SqrtCentimeter and SqrtGram as the base units for cgs. Then, Centimeter and Gram can be easily defined as derived units.

Pros and Cons:

  1. It's intuitive to define one of these unit systems. I'm not sure how much that matters, as it would be better to just provide them in dimensioned anyway.

  2. Printing is easy. We can use the same macro-defined Display implementation for everything. If we switch to option 2, then the default would be to print centimeters as "sqrtcm^2" or something. But, we have a flag to not automatically implement these and could special case them, so it isn't a big deal.

  3. Unit conversions are unintuitive. Say you want to define the conversion from cgs to mks. To do the cm -> m part, you have to do something like let cm_fac = 0.01.sqrt().powf(Centimeter.to_i32()). The need for the sqrt there may mess people up, whereas if the base unit is SqrtCentimeter it may be more clear.

  4. It increases the cognitive overhead for someone making their own unit system. Say someone wants a simple Foot, Minute system. It'd be better for them to not have to wonder about the P1s everywhere.

I think I've mostly convinced myself to remove this feature, but I'm open to any feedback first.

Error post-processor

Dimensioned has terrible error messages. For example,

extern crate dimensioned as dim;
use dim::si::{M, S};

fn main() {
    M + S;
}

produces the following message:

 --> tests/dim-ops.rs:22:9
   |
22 |     M + S;
   |         ^ expected struct `dim::<unnamed>::PInt`, found struct `dim::<unnamed>::Z0`
   |
   = note: expected type `dim::si::SI<_,
      dim::<unnamed>::TArr<dim::<unnamed>::PInt<dim::<unnamed>::UInt<dim::<unnamed>::UTerm, dim::<unnamed>::B1>>,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::ATerm>>>>>>>>`
   = note:    found type `dim::si::SI<f64,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::PInt<dim::<unnamed>::UInt<dim::<unnamed>::UTerm, dim::<unnamed>::B1>>,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::TArr<dim::<unnamed>::Z0,
      dim::<unnamed>::ATerm>>>>>>>>`

where I have added line breaks to increase legibility.

I would like it to look something like this:

 --> tests/dim-ops.rs:22:9
   |
22 |     M + S;
   |         ^ expected  `m`, found `s`
   |
   = note: expected type `f64 m`
   = note:    found type `f64 s`

I imagine this could be accomplished by having a post-processor that creates a short Rust program which just creates variables with those type signatures and then prints them, then replaces the text.

It might only end up working for units created in dimensioned, but that's a start. It might also get hard to parse with other types mixed in there. Perhaps there's a better way?

Bad tutorial link

The tutorial link on the front page of the docs just redirects back to the same place. I guess it is supposed to be http://paholg.com/project/dimensioned/ ?

The website link in README.md also goes to docs, when maybe it should be the project page too, but that could go either way...

Add support for absolute values

It would be lovely to be able to take the absolute value of a dimensioned number. I am not sure how to do this best, but would be happy to work on a pull request. Sadly, abs does not seem to be in a trait in the standard library, so we'd need specific implementations for basically every primitive type as far as I can see?

Rename Dim

A pedantic breaking changing. Please close if you believe this is too much breakage.

The current struct has dimensions, but it seems awkward to also call it Dim. I believe the current Dim struct should be renamed to Quantity . Given a system of quantities (SI) each quantity can be defined in terms of a quantity dimension and base quantity.

Use from stable Rust

Dimensioned currently only works in non-stable Rust. Is it possible to disable some features when building on the stable channel, or are those features fundamentally necessary for the library implementation?

    Updating registry `https://github.com/rust-lang/crates.io-index`
 Downloading dimensioned v0.4.0
 Downloading peano v1.0.1
 Downloading num v0.1.28
   Compiling peano v1.0.1
   Compiling rustc-serialize v0.3.16
   Compiling winapi v0.2.5
   Compiling libc v0.2.2
   Compiling winapi-build v0.1.1
   Compiling advapi32-sys v0.1.2
   Compiling rand v0.3.12
   Compiling num v0.1.28
   Compiling dimensioned v0.4.0
/Users/brendan/.multirust/toolchains/stable/cargo/registry/src/github.com-0a35038f75765ae4/dimensioned-0.4.0/src/lib.rs:13:1: 13:54 error: #[feature] may not be used on the stable release channel
/Users/brendan/.multirust/toolchains/stable/cargo/registry/src/github.com-0a35038f75765ae4/dimensioned-0.4.0/src/lib.rs:13 #![feature(optin_builtin_traits, zero_one, const_fn)]
                                                                                                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/brendan/.multirust/toolchains/stable/cargo/registry/src/github.com-0a35038f75765ae4/dimensioned-0.4.0/src/lib.rs:14:1: 14:25 error: #[feature] may not be used on the stable release channel
/Users/brendan/.multirust/toolchains/stable/cargo/registry/src/github.com-0a35038f75765ae4/dimensioned-0.4.0/src/lib.rs:14 #![feature(type_macros)]
                                                                                                                           ^~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 2 previous errors
Could not compile `dimensioned`.

To learn more, run the command again with --verbose.

Strange issue with Root.

I'm working on the rmdim branch, and have come across something I don't understand.

The issue crops up in this test, partially reproduced here:

let x = 2.0*M;
let x2 = 4.0*M*M;

assert_eq!(x2, x.powi(P2::new()));
assert_eq!(x, x2.sqrt());
// fixme: This fails. Why? It should be the same as calling sqrt().
// assert_eq!(x, x2.root(P2::new()));

I can call x2.sqrt() with no issues. That implementation looks like this:

use typenum::P2;
impl<T> Sqrt for T
    where T: Root<P2>
{
    type Output = <T as Root<P2>>::Output;
    fn sqrt(self) -> Self::Output {
        self.root(P2::new())
    }
}

However, if I try to call x2.root(P2::new()), I get an infinite recursion issue. But that's all that sqrt() does.

Also, the Pow trait is implemented essentially identically to the Root trait, and it has no issues.

Those impls are in the macro, here.

Re-add rustfmt and clippy to TravisCI build

Clippy was removed in #39 as it kept breaking things, but it can now be installed with rustup, so that is hopefully no longer an issue.

Rustfmt was removed in #44 due to #46, but hopefully we can get it working again.

Unit conversions

I have been looking into how to make converting between unit systems as painless as possible.

Here is an example showing where I am so far.

Basically, every unit system would have its own From trait that would let you convert from it into another system.

I have two main issues with this method

  1. It's cumbersome to implement. It's not so bad for systems of two units, but once you get up to the 7 that SI has, or have to deal with systems that expression dimensions in different powers (such as CGS, or a system with gallons as a base unit), it gets pretty messy. I don't mind for the unit systems that ship with dimensioned, but it should be easy for users to create their own systems and convert between them.

  2. It lacks genericity. In order to implement the From traits, I had to restrict it to types that can be multiplied by an f64 (could have also chosen f32) and not change type. What's worse, is that once implemented for, say, f64, I don't believe it possible to also implement the conversion for f32. At least, not without abandoning all genericity and doing:

    impl<Meter: Integer, Second: Integer> ms::FromMS<Meter, Second> for fs::FS<Meter, Second> {
        fn from_ms(from: Dim<ms::MS<Meter, Second>, f32>) -> Dim<Self, f32> {
            Dim::new(from.0 * 3.281)
        }
    }

    This would restrict Dim to only being used with primitives if you want access to unit conversions. One could argue that that's how Dim should be used, but it requires every container you put Dim in to be incredibly generic. I have yet to see a matrix or vector library that I have not written that is generic enough to be used with Dims.

Validate and ideally repace uses of `mem::transmute`

There are two places in the make_units! macro where mem::transmute is used; for implementing Index and IndexMut. Both require going from &V to &$System<V, U> for some U.

I think that the current use should be safe, but would like external validation. It would also be nice to replace it with something less dangerous.

Implement `max` and `min` functions for primitive types that have them

f32, f64, etc. have functions called max and min that are defined to return the higher/lower non-NaN number. AFAICT it's not possible to actually call them on unit types without manually unpacking with either .value_raw or creating a dimensionless quantity and derefing. Please consider adding max and min functions on the unit types if the primitive type has them.

support std::iter::Sum.

It would be lovely to implement std::iter::Sum for types that also implement it, so I could use vec.iter().sum().

Change dimensioned to use #![no_std]

Change dimensioned to use #![no_std] so that it may be used by other #![no_std] crates. Currently the only requirement appears to be the DimToString functionality and if it can be changed to work with core::fmt::Formatter then the current std requirements could be removed.

Question: working with nalgebra

I feel so inept at this. I appreciate the help.

I'm trying to create a helper function that will take an array slice and create a dimensioned nalgebra::Vector3 out of it... but i'm failing. This is what I've got so far:

fn dim_vector3<L, R>( unit_const :L, arr :[R ;3] ) ->
  na::Vector3<dim::typenum::Prod<L, R>>
where
  L: std::ops::Mul<R>
{
  na::Vector3::new( 
    unit_const * arr[0], 
    unit_const * arr[1], 
    unit_const * arr[2]
  )
}

any insight appreciated :)

travis fails to build

The trouble is that cargo fmt is currently breaking the code. See:

rust-lang/rustfmt#2880

Presumably this is a newish bug in cargo, since the last commit on master passed, but for now it's making pull requests fail.

Imperial alternative to SI?

It would be nice to include an equivalent unit system to SI that uses imperial units.

I'm not sure what units it should use, though. For example, do we use feet for length? Inches? Miles? Furlongs?

Given that, it might make more sense that people would want to define their own with whatever units make the most sense.

I would still like to define at least one, to give a nice example of converting to and from SI, and for interoperability, so that even if someone makes their own unit system, the could define a conversion to ours to work with other crates.

For reference, SI has 7 base units: meter, kilogram, second, ampere, kelvin, candela, mole.

Feature request: Celsius

I realize that the documentation for ucum says:

There are a few classifications of units that UCUM defines but that it does not make sense to define here, and so we do not.

Units that require conversions that involve more than multiplication. These include some temperature units (such as degrees Celcius)

But easy conversion between Celsius and Kelvin is something I expected to be able to do. Perhaps it doesn’t make sense to implement it in the ucum unit system, but perhaps having an additional unit system would make sense. (maybe it should be called “misc”?).

Here is my pathetic attempt to implement this (which doesn’t compile).

mod ucum_plus {
  make_units! {
    UCUM_PLUS;
    ONE: Unitless;

    base {
      DEGC: DegreeCelsius, "°C", Temperature;
    }
    derived {
    }
    constants {
    }
    fmt = true;
  }

  pub use self::f64consts::*;

  use dim::typenum::{Integer, Z0};
  use std::convert::From;
  use dim::ucum;

  impl<V, Temperature> From<ucum::UCUM<V, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]>>
    for UCUM_PLUS<<<<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output as dim::typenum::Pow<i32>>::Output, tarr![Temperature]>
    where
      V: Mul<f64> + dim::typenum::Pow<i32>,
      Temperature: Integer,
      <V as dim::typenum::Pow<i32>>::Output : Sub<f64>,
      <<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output : dim::typenum::Pow<i32>
  {
    fn from(other: ucum::UCUM<V, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]>) -> Self {
      let pow = Temperature::to_i32();

      let kelvin = other.value_unsafe.powi(-pow);
      let celcius = kelvin - 273.15;

      UCUM_PLUS::new( celcius.powi(pow) )
    }
  }

  impl<V, Temperature> Into<ucum::UCUM<<<<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output as dim::typenum::Pow<i32>>::Output, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]>>
    for UCUM_PLUS<V, tarr![Temperature]>
    where
      V: Mul<f64> + dim::typenum::Pow<i32>,
      Temperature: Integer,
      <V as dim::typenum::Pow<i32>>::Output : Add<f64>,
      <<V as dim::typenum::Pow<i32>>::Output as std::ops::Add<f64>>::Output : dim::typenum::Pow<i32>
  {
    fn into(self) -> ucum::UCUM<<<<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output as dim::typenum::Pow<i32>>::Output, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]> {
      let pow = Temperature::to_i32();

      let celsius = self.value_unsafe.powi(-pow);
      let kelvin = celsius + 273.15;

      ucum::UCUM::new( kelvin.powi(pow) )
    }
  }

  #[cfg(test)]
  mod tests {
    use super::*;

    #[test]
    fn conversion_test() {
      let c = 30. * DEGC;
      let k = 400. * ucum::K;
      let diff = k - c;

      assert_eq!(*(diff/K), 96.85);
    }
  }
}

Use #[rustc_on_unimplemented]

With #[rustc_on_unimplemented(mesage = "xxx", label = "xxx")], it is possible to customize error messages if an implementation for a trait is not found. Maybe this can produce better error messages.

It is available under #![feature(on_unimplemented)], which unfortunately requires nightly.

Conversion from primitives not working with integer values

NB: I'm entirely prepared to be told I'm doing it wrong.

I have created a unit system with a single dimension to represent pixels in an image like so:

pub mod pixel_space {
    make_units! {
        PIX;
        ONE: Unitless;

        base {
            PX: Pixel, "px", Length;
        }

        derived {
            PX2: Pixel2 = (Pixel * Pixel), Area;
        }

        constants {

        }

        fmt = true;
    }
    pub use self::f64consts::*;
}

In a unit test I then attempt to create a Pixel<isize> - as we'll always have an integral number of pixels -
like so:

#[cfg(test)]
mod test {
    use super::pixel_space::{Pixel, PX};

    #[test]
    fn create_integer_value() {
        let x : Pixel<isize> = 42 * PX;
        assert_eq!(x.value_unsafe, 42);
    }
}

Which promptly fails to compile with:

$ cargo test
   Compiling firkin v0.1.0 (file:///home/tcc/projects/firkin)
error[E0277]: the trait bound `{integer}: std::ops::Mul<units::pixel_space::PIX<f64, dim::<unnamed>::TArr<dim::<unnamed>::PInt<dim::<unnamed>::UInt<dim::<unnamed>::UTerm, dim::<unnamed>::B1>>, dim::<unnamed>::ATerm>>>` is not satisfied
  --> src/units.rs:29:32
   |
29 |         let x : Pixel<isize> = 42 * PX;
   |                                ^^^^^^^ the trait `std::ops::Mul<units::pixel_space::PIX<f64, dim::<unnamed>::TArr<dim::<unnamed>::PInt<dim::<unnamed>::UInt<dim::<unnamed>::UTerm, dim::<unnamed>::B1>>, dim::<unnamed>::ATerm>>>` is not implemented for `{integer}`
   |
   = help: the following implementations were found:
             <dim::<unnamed>::TArr<V, A> as std::ops::Mul<Rhs>>
             <bool as std::ops::Mul<dim::si::SI<V, U>>>
             <bool as std::ops::Mul<dim::ucum::UCUM<V, U>>>
             <bool as std::ops::Mul<dim::mks::MKS<V, U>>>
           and 298 others

error: aborting due to previous error

error: Could not compile `firkin`.

Note that doing the same thing with a floating-point value works as expected:

#[cfg(test)]
mod test {
    use super::pixel_space::{Pixel, PX};

    #[test]
    fn create_real_value() {
        let x : Pixel<f64> = 42.0 * PX;
        assert_eq!(x.value_unsafe, 42.0);
    }
}

Am I using it wrong, or is this an issue?

derived! incompatible with #[deny(missing_docs)]

Even if I add a doc comment above the macro invocation, I still get a "missing documentation for a type alias" error.

Eg:

#![deny(missing_docs)]

use dimensioned::si::{self, SI};
/// Some doc comment
derived!(si, SI: InverseMeter3 = Unitless / Meter3);

Serde support

Serde support (presumably behind a feature flag) would be really useful to me. I may take a stab at implementing it if I have time.

I feel that it's not necessary to serialize the unit, to save space. This means that if you changed the units of a field, it might deserialize without error. Do you think that's the right choice?

Update `approx` dependency

We use a very old version of approx, and it has been through some breaking changes since then. It would be nice to update to the new version.

Approx is used for comparing constants in tests, ensuring that converting between them gives us almost identical values.

Use conventional letter case

Instead of si::M, I would prefer to stick to the original letter case, i.e., si::m. I suppose it is not Rust-style, however, the correct letter case would avoid some clashes of derived and combined units. See the following:

Symbols are case-sensitive, so the meaning of an SI symbol can be changed if you substitute an uppercase letter for a lowercase letter.
Correct: mm (for millimetre, which means 1/1000 of a metre)
Incorrect: MM or Mm (M is the symbol for the prefix mega-, which means one million; a megametre is a million metres)

Implement various functions on primitives.

It came up in #41 that it would be nice to have functions like abs(), sqrt(), etc. implemented on $System<f64, U> (and friends) directly, and not just through traits, so that they could be called without the traits in scope.

Example for simple case of returning Length/Time

Apologies if this is documented somewhere and I can't find it. I'm trying to write a distance function that currently returns a distance in meters. I'd like to use this crate to add type safety to this return value, with the eventual goal of representing the distance in feet or other units, or combining it with time units to create speeds/velocities.

Your example shows how to write a speed function. Would you mind demonstrating how one might write a distance function? I.e.:

fn distance(p1: Point, p2: Point) -> f64 {
    // Assume we crunch some numbers and get a distance in meters
    meters
}

I tried making the f64 a Length, but additional type parameters are needed, and if I try braking the build to see what type signature is expected, I get a huge ugly thing that doesn't really give me any clarity. :)

Thanks.

Question: accepting generic dimension for calculation in specific units

(I'm sorry. I'm very new to rust and having a hard time understanding the type system.)

What I'm trying to achieve is a method on a struct that accepts any temperature, and uses it in a calculation to return a unitless value.

I'm trying to do something like this:

impl TemperatureDependence for MyStruct {
  fn apply<T>(&self, n :Vector3<f64>, temperature :T) -> Vector<f64> 
  where T :Temperature {
    // --snip--
    // this should be unitless.. not sure if I should use dimensioned::si::Unitless... 
    let f :f64 = (temperature - 20 * si::K) / si::K;
    // --snip--
    // returns a vector based on this
  }
}

But I get the error:

binary operation - cannot be applied to type T
note: T might need a bound for std::ops::Sub

I feel like i'm missing something, because i don't actually want to make this THAT generic. I feel like any type that implements trait Temperature should be able to be converted into Kelvin... so what gives? How do i do this?

Thanks in advance.

Any good idea how to integrate with `ordered_float`?

Sometimes we want to sort a list of physical quantities. For such purpose, ordered_float is useful; we want to handle with Vec<Meter<OrderedFloat<f64>>>. However, useful constants like M and KG are defined only for primitive types, so there seems no really easy way to multiply quantities. Any idea how to deal with such situation? Currently I'm thinking of making a module named ordered_float_f64_consts or something, copying entire contents of f64consts (expanded by cargo-expand) to my project. Maybe it would be useful if this library provides a macro for defining arbitrary-type constants for existing unit system.

Thanks for this amazing project, by the way! I'm enjoying exploring the type-safety world of physical computations.

Potentially switch to fully composable units without unit systems

We should really try moving this to typenum. Perhaps while we're at it, we may want to get away from the dimensions-tuple and use a more dynamic approach that allows us to more freely match unit types. I'm thinking about a mapping from unit to exponent, which could be implemented

  • very naively using a type-level linked list of (unit, exponent) tuples, where applying a (unit, exponent) type means a linear search for each type, though this can be quite slow as our number of units grows; merging would be in O(n²)
  • a little bit more sophisticated as a binary tree, which would however require an ordering, but would merge in O(n log n)
  • use a GenericArray-like approach to create generic arrays of (unit, exponent) tuples, perhaps with a default (NoUnit, Zero) value to create a HashMap-like structure, which would merge in O(n).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.