| Crates.io | ferrunitas |
| lib.rs | ferrunitas |
| version | 0.4.0 |
| created_at | 2025-09-08 14:51:55.410362+00 |
| updated_at | 2025-09-13 13:54:03.991397+00 |
| description | A type-safe unit conversion library with compile-time dimensional analysis |
| homepage | |
| repository | https://github.com/marekhummel/ferrunitas |
| max_upload_size | |
| id | 1829366 |
| size | 285,638 |
A Rust library for compile-time dimensional analysis and unit conversions. Ferrunitas provides type safety for physical quantities and prevents dimensional errors at compile time through Rust's powerful type system. The name "Ferrunitas" combines the Latin "ferrum" (iron, referencing Rust) and "unitas" (oneness, unity). Refer to this document for the base information of the implementation.
Measure<Metre>) or generic quantities (Length)Angle vs Information)std feature is used its just for propagation to dependencies.f32 or f64 based on feature (f64 stays default)use ferrunitas::system::*;
use ferrunitas::{Measure, Unit};
fn main() {
// Create measures with specific units
let distance = 100 * Metre;
let time = Second::new(10.0);
// Convert between compatible units
let feet: Measure<Foot> = distance.convert();
let minutes: Measure<Minute> = time.convert();
println!("Distance: {:.2} or {:.2}", distance, feet);
println!("Time: {:.2} or {:.2}", time, minutes);
// Arithmetic operations with automatic dimensional checking
let velocity: Velocity = distance / time;
let speed_mps = velocity.as_measure::<MetrePerSecond>();
let speed_mph = velocity.as_measure::<MilePerHour>();
println!("Speed: {:.3} or {:.3}", speed_mps, speed_mph);
}
// For dimensional formatting
use ferrunitas::common::{format_dims, format_unit_dims};
// For access to all definitions to units, quantities and prefixes
use ferrunitas::system::*;
// Crate root access to most common struct Measure and trait Unit to make methods available
use ferrunitas::{Measure, Unit};
// Macros for own definitions (typenum consts for quantity macro)
use ferrunitas::typenum_consts::*;
use ferrunitas::{prefix, quantity, unit};
Ferrunitas offers two main ways to work with physical values (they are compatitble with each other regarding arithmetics):
Measures - Values with specific units:
let mass = Kilogram::new(5.0); // Measure<Kilogram>
let length = 10 * Foot; // Measure<Foot>
let sum = mass + Gram::new(500.0); // Addition works across compatible units
Quantities - Dimensioned values without specific units:
let mass: Mass = Kilogram::new(5.0).into_q(); // Convert to quantity
let length: Length = Foot::new(10.0).into_q(); // Convert to quantity
let force: Force = mass * acceleration; // Multiplication creates new quantity
// Multiple ways to convert units
let liters = Litre::new(30.64);
// Via quantity (intermediate step)
let volume: Volume = liters.into_q();
let cubic_cm: Measure<CubicCentimetre> = volume.as_measure();
// Direct conversion
let cubic_cm_direct: Measure<CubicCentimetre> = liters.convert();
// One-line conversion
let cubic_cm_oneline = Volume::convert::<Litre, CubicCentimetre>(30.64);
// All operations are dimensionally checked at compile time
let mass = Kilogram::new(2.0);
let acceleration = MetrePerSecondSquared::new(9.81);
let force: Force = mass * acceleration; // F = m·a
let distance = Metre::new(5.0);
let work: Energy = force * distance; // W = F·d
let time = Second::new(2.0);
let power: Power = work / time; // P = W/t
println!("Power: {:.2}", power.as_measure::<Watt>());
Ferrunitas provides flexible function signatures for different use cases:
// Accept specific units
fn kinetic_energy(mass: Measure<Gram>, velocity: Measure<MetrePerSecond>) -> Measure<Joule> {
(0.5 * mass * velocity * velocity).as_measure()
}
// Accept any unit of a quantity type
fn calculate_force(
mass: Measure<impl Unit<Quantity = Mass>>,
acceleration: Measure<impl Unit<Quantity = Acceleration>>,
) -> Measure<Newton> {
(mass * acceleration).as_measure()
}
// Work directly with quantities
fn calculate_acceleration(velocity_change: Velocity, time: Time) -> Acceleration {
velocity_change / time
}
Define your own units using the provided macros:
use ferrunitas::{quantity, unit, prefix, typenum_consts::*};
// Define a new quantity (7 SI base dimensions: M, L, T, I, Th, N, J)
quantity!(MyLength: M Z0, L P1, T Z0, I Z0, Th Z0, N Z0, J Z0);
// Define a new quantity with tagging (requires `quantity_tags` feature to have any effect)
quantity!(SpecialAngle: M Z0, L Z0, T Z0, I Z0, Th Z0, N Z0, J Z0; marked);
// Define a new prefix
prefix!(Magic, 42, "M");
// Define new units
unit!(base: Elbow, "elbow", MyLength; prefixable);
unit!(derived: Yard, "yd", (0.9144, Metre));
unit!(prefix: Magicmetre, Magic, Metre);
// Compound units with quantity tagging (requires `quantity_tags` feature to have any effect)
unit!(compound: MyAngleUnit, "mau", [(Radian, P1)]; marked SpecialAngle);
When the quantity_tags feature is enabled, Ferrunitas can distinguish between quantities that are dimensionally identical but represent different physical concepts:
// Enable with: cargo build --features="quantity_tags"
use ferrunitas::system::*;
use ferrunitas::Unit;
// These are both dimensionless, but represent different concepts
let data = Bit::new(8.0);
let angle = Radian::new(1.0);
// Without quantity_tags: these can be added directly
// With quantity_tags: compilation error - different quantity types!
// let invalid = data + angle; // Error with quantity_tags enabled
// Instead, you need to explicitly specify the target quantity:
let combined = data.into_q().specify::<Dimensionless>() +
angle.into_q().specify::<Dimensionless>();
// This helps prevent mixing semantically different values
let area1 = SquareMetre::new(4.0);
let area2 = SquareMetre::new(1.0);
let solid_angle = Steradian::new(0.5);
// With tags: Need to specify the result type, so that they can be added
let result = (area1 / area2).specify::<SolidAngle>() + solid_angle;
Ferrunitas supports serialization and deserialization of both quantities and measures using Serde, if the serde feature is enabled:
use ferrunitas::{Measure, system::*};
use serde_json;
// Serialize a measure with specific unit
let potential = 5.5 * Volt;
let json_measure = serde_json::to_string(&potential).unwrap();
let json_quantity = serde_json::to_string(&potential.into_q()).unwrap();
// Result M: {"value":5.5,"unit":"ferrunitas::system::defs::electromagnetism::Volt"}
// Result Q: {"value":5.5,"dimensional_vector":[1,1,-3,-1,0,0,0],"tag":"()"}
// Deserialize back to the same measure type
let restored_measure: Measure<Volt> = serde_json::from_str(&json_measure).unwrap();
let restored_quantity: Potential = serde_json::from_str(&json_quantity).unwrap();
assert_eq!(potential, restored_measure);
assert_eq!(potential.into_q(), restored_quantity);
The examples/ directory contains comprehensive usage examples:
basics.rs - Core concepts: measures, quantities, conversions, and arithmeticfunctions.rs - Different patterns for writing functions with dimensional typesadvanced.rs - Custom unit definitions using macrosserde_example.rs - Serialization and deserialization with Serdemisc.rs - Dimensional introspection, debugging utilities, and quantity tagging demonstrationRun examples with:
cargo run --example basics
cargo run --example functions
cargo run --example advanced
cargo run --example misc
# Features
cargo run --example serialization --features="serde"
cargo run --example misc --features="quantity_tags"
Real, either f32 or f64 depending on installed feature; typical floating point caveats apply, see examples/misc.rs.Contributions are welcome! Please feel free to submit a pull request or open an issue for any suggestions or improvements.
This project is licensed under the Apache-2.0 License.