Good point. Any name suggestion?
Heh. I know
typenum/dimensioned invents new names for each of its type aliases (
Mod, …), which I’ve never liked because it feels totally arbitrary sometimes which one is the trait versus the type alias (
Negate…) and the naming game never ends as you add more traits (
In recent projects I’ve been trying various forms of Hungarian notation like
TyMul (still can’t figure out which I prefer). Almost surely this would make most rust programmers recoil in horror, but I see no better solution to the problem!
My previous versions used
UDimensionless (for “units”) or
PDimensionless (for “proofs”) but neither sounded very satisfying.
For a closed set of traits, a type macro is another option, probably in tandem with type aliases defined somewhere (emitted by the macro in place of associated type syntax, so that docs are comprehensible).
(I tend to forget this option exists because intellij-rust chokes on type macros =P)
If the user introduces units as CGS because that’s what your UI for your client must do, and Rust only supports SI, then at some point, someone, is going to have to convert those units. If conversion errors are not type errors, and that someone makes a mistake… then BOOM.
Ok, I have just uploaded on crates:
Things are of course bound to break, change name, etc.
I’m happy to see that you went with a library + tooling based approach.
Oh, I might still come up with a RFC one of these days
Right now, my problem is that the simplest proposal I could come up with would require a notion of equivalence between types, which might be very non-trivial in conjunction with traits.
I basically agree with this comment with respect to the RFC: Any RFC for Units of Measure?
If the type-system isn’t powerful enough to implement this as a library, well, then maybe it should be.
Basically, if you want to bolt units into the language, you are going to need a really good motivation about why the type-system cannot be extended to support allowing all of this to be in a library.
I don’t want to be too pessimistic here, but the bar is probably going to be really high. Maybe on the level of needing a really good RFC proposing type-system extensions, and that RFC being rejected with the recommendation of pursuing this one instead.
If someone sends a really good RFC for those type-system extensions, however, what could happen is that the RFC will be postponed, and not rejected, because right now there are major refactorings of the trait system being done. That might leave all of this “hanging”.
I don’t know, maybe you should try to write a mini-RFC about what type-system extensions would be needed to support this in a library, and chat with @nikomatsakis about whether it might be possible to explore those in-tree or in Chalk.
Some years ago I created this complete C++ library: Its associated wiki pages are an extensive but incomplete documentation of that library. I think it may be taken into account for a similar Rust library or language extension. It uses a macro-based approach, quite different from the one used by Boost.Units.
Since these are just different units for the same dimensions, I don’t think they should actually be restricted anywhere. It should be possible to just say
(Versta::new(5) / Microfortnight::new(2)).unify().into::<MetersPerSecond>() and get the result. This would require to have associated conversion factor in the unit declaration, but that should be easy.
It would be perfect to eventually support even
Meter::try_from_str("2 nmi") resulting in
Meter(3704), but to make that extensible, there would have to be some global registry of unit parsers (support for global registries of factories and/or static instances would be a useful general extension for rust).
Also as insane as it may sound to us who learned to do physics in SI, there actually still are people who do engineering calculations in imperial units, often using either slug for weight (with force-pound) or poundal for force (with mass-pound).
The representation is a paremter, that’s not a problem. It would want to support dynamic conversion factors though.
This, however, means, that every user will have their own system of dimensions. Because one will have just length and the other will have parallel length and perpendicular length (the ratio between them is angle). And yet another may have just one dimension for both length and time, because they are using natural units (where c = 1, dimensionless).
The unique constants that Boost uses only need to be unique in such system and then everybody either uses the predefined one, or defines their own that is independent of the others, so it’s not that big problem. It does make things simpler if you can combine the systems freely though so I am not saying the constants don’t complicate anything.
Some values cannot be multiplied or divided (e.g. ºC, ºF, pH, dB). We do not attempt to differentiate between units that can be multiplied/dived and units that can, although this might happen in a future version.
This is not, actually, about units at all, but about positions in a coordinate system. The time on the system clock is in seconds, but dividing it does not make any sense either.
Therefore I think the correct approach is to have a separate value type (say
Position), where the operators defined are restricted to
Position + Measure → Position,
Position - Measure → Position,
Position - Position → Measure (and comparisons).
Then °C would be just special in that it would be a unit for
Position only and its corresponding
Measure would have automatically unit of K (Kelvin). In fact, the unit would probably be K in both and the °C would be special alias when combined with suitable declaration of “coordinate system” in the Position.
This is how
std::time::Duration work and for adoption it would be great if those became
This does not apply to
dB, where the reason multiplication makes little sense is they are not linear (so addition still does make sense, unlike °C, or wall times), but those are much more corner cases.
I’d like to chime in and suggest that the Unified Code for Units of Measure (UCUM) at least be considered here. My company relies on this spec for dealing with units across our stack (JS, Ruby, Rust, Scala, .NET and probably some others). We have an internal Rust library that we use for dealing with this (although it may not be super “rust-y”) that I planned on open-sourcing once it stabilized a bit. I’d be happy, however, to abandon that if I could use a standard Rust library for that. I do know dimensioned has some UCUM support, but it doesn’t quite fit our needs that I can tell (mainly parsing a unit string like
"[lb_av]/100[gal_us]" into some Rust struct/whatever that represents a single
Unit of sorts); that’s probably not a discussion for here.
Anyway, I just wanted to put it out there for consideration.
This interesting article, dated May 24th, 2018, is “A survey of existing designs and implementations for automatic conversion and verification of units of measurement in computer programs.”: https://gmpreussner.com/research/dimensional-analysis-in-programming-languages
Total ordering in general is not, and won’t be, feasible in Rust without major rearchitecting the core language. In other words: don’t hold your breath
A simple but crucial counterexample is what happens when you use e.g.
f64 values as components of a more complex type e.g.
A * B. Floats cannot be totally ordered due to the existence of the various
Love to have Units of Measurement in Rust. Seems it can be achieved by creating compiler plugins for it
I would also be happy to see better support in Rust for units of measurement. Since edition 2018 is around the corner, and features such as trait specialisation are more fleshed out now, anyone have any update on which direction we should go with this, and if there have been any promising experiments in the last ~3 months?
I think the biggest unknown is how far a library solution will be able to get with const generics, so anything moving that forward would probably help. (There might well be language changes needed too, but hopefully few, and it’s currently unknown which.)
As far as I can tell, the only functionality it’s not possible to construct in a library (after const generics is complete) is canonicalisation of dimension types (e.g. such that
m / s^2 is the same as
s / (s^3 / m)). To support this in the language, you either need to make units of measure first-class, with the canonicalisation logic inside the language itself, or provide type-level functions (just
type -> type, not necessarily full HKTs). (I wrote a little about this.)
I don’t think that we should allow
3.0[mm] + 5.0[ft], even though both values represent length. Rust generally avoids implicit conversions, so I don’t think it’s a good idea to introduce such large source of implicitness via units system. We may even go further and distinguish between unit and how it’s stored in memory, so the same
Meter unit will be usable with
u32, etc. and
1u32[m] + 0.1f32[m] will produce an error, though I am not sure if it’s worth the associated complexity.