{"id":37156215,"url":"https://github.com/kelledge/uom","last_synced_at":"2026-01-14T18:30:19.308Z","repository":{"id":310132683,"uuid":"1038779987","full_name":"kelledge/uom","owner":"kelledge","description":"Type-safe, expressive, and efficient handling of physical quantities in Go.","archived":false,"fork":false,"pushed_at":"2025-08-27T03:25:22.000Z","size":54,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-27T11:53:27.939Z","etag":null,"topics":["quantities","scientific-computing","units","units-of-measure","units-of-measurement"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kelledge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-15T20:00:43.000Z","updated_at":"2025-08-27T03:25:25.000Z","dependencies_parsed_at":"2025-08-16T00:37:01.816Z","dependency_job_id":"d86a6a77-7a8b-4d67-88dc-ce9eb42baafa","html_url":"https://github.com/kelledge/uom","commit_stats":null,"previous_names":["kelledge/uom"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kelledge/uom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelledge%2Fuom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelledge%2Fuom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelledge%2Fuom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelledge%2Fuom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kelledge","download_url":"https://codeload.github.com/kelledge/uom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelledge%2Fuom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28430832,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T16:38:47.836Z","status":"ssl_error","status_checked_at":"2026-01-14T16:34:59.695Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["quantities","scientific-computing","units","units-of-measure","units-of-measurement"],"created_at":"2026-01-14T18:30:15.114Z","updated_at":"2026-01-14T18:30:19.243Z","avatar_url":"https://github.com/kelledge.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go Units of Measure\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/kelledge/uom.svg)](https://pkg.go.dev/github.com/kelledge/uom)\n[![CI](https://github.com/kelledge/uom/actions/workflows/ci.yml/badge.svg)](https://github.com/kelledge/uom/actions/workflows/ci.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/kelledge/uom)](https://goreportcard.com/report/github.com/kelledge/uom)\n\n**Type-safe, expressive, and efficient handling of physical quantities in Go.**  \n\n**NOTE: Very much still in active development. API refinements are likely.**\n\n---\n\n## Goals\n\n1. **Dimensions as first-class citizens** — Compile-time and Runtime checks prevent mixing incompatible quantities (e.g., adding mass to length).  \n2. **Type safety** — The compiler warns you when passing or defining invalid physical quantities.\n3. **Minimal boilerplate** — Adding a new unit is quick and painless.\n4. **Readable and expressive** — Expressions with physical quantities remain easy to follow, even without operator overloading.  \n5. **Reasonable performance** — Dimension safety shouldn’t boil the ocean.\n\n---\n\n## Why This Matters\n\n- **Catches mistakes early** — At compile time where possible, at runtime otherwise.  \n- **Produces clear, informative errors** — So you know *exactly* where the mismatch is.  \n- **Keeps code readable** — Code should remain simple to write and inspect for correctness.\n\n[The Cost of Getting it Wrong](https://en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure)\n\n---\n\n## Usage and Examples\nHere are is a minimal example that outline the usage.\n\n### Step 1: Defining Dimensions\nWe need a distinct go type and this involes implementing the uom.Dimension interface.\n\nNo one likes implementing interfaces, but fortunately you do not need many dimension types to fill out a very complete system of units.\n\nShips with a very reasonable set of dimensions to work from. Only fringe/esoteric uses should be expected to define their own.\n```go\nTime struct{}\nfunc (Time) Dimension() uom.DimInt { return uom.NewDimInt(uom.DimensionSpec{Time: 1}) }\nfunc (Time) Name() string          { return \"Time\" }\n\nLength struct{}\nfunc (Length) Dimension() uom.DimInt { return uom.NewDimInt(uom.DimensionSpec{Length: 1}) }\nfunc (Length) Name() string          { return \"Length\" }\n\nArea struct{}\nfunc (Area) Dimension() uom.DimInt { return uom.NewDimInt(uom.DimensionSpec{Length: 2}) }\nfunc (Area) Name() string          { return \"Area\" }\n\nVolume struct{}\nfunc (Volume) Dimension() uom.DimInt { return uom.NewDimInt(uom.DimensionSpec{Length: 3}) }\nfunc (Volume) Name() string          { return \"Volume\" }\n```\n\n### Step 2: Defining Units\n```go\nvar (\n    Second = uom.DefineUnit[dim.Time](\"s\", 1.0)\n    Hour   = uom.DefineUnit[dim.Time](\"h\", 3600.0)\n\n    Meter = uom.DefineUnit[dim.Length](\"m\", 1.0)\n    Inch  = uom.DefineUnit[dim.Length](\"in\", 0.0254)\n\n    SquareMeter = uom.DefineUnit[dim.Area](\"m^2\", 1.0)\n    CubicMeter = uom.DefineUnit[dim.Volume](\"m^3\", 1.0)\n)\n```\n\n### Step 2.5: Deriving Units\n```go\nvar (\n    Feet = uom.DeriveUnit[dim.Length](\"ft\", uom.U(Inch).MulScalar(12))\n    Mile = uom.DeriveUnit[dim.Length](\"mile\", uom.U(Feet).MulScalar(5280))\n\n\tInchesPerSecond = uom.DeriveUnit[dim.Velocity](\"in/s\", uom.U(Inch), uom.U(Second).Per())\n    MilesPerHour    = uom.DeriveUnit[dim.Velocity](\"mph\", uom.U(Mile), uom.U(Hour).Per())\n\n    CubicInch = uom.DeriveUnit[dim.Volume](\"in^3\", uom.U(Inch).Pow(3))\n    Gallon    = uom.DeriveUnit[dim.Volume](\"gallon\", uom.U(CubicInch).MulScalar(231))\n)\n```\n\n### Step 3: Using Quantities and Expressions\n```go\ntype Cylinder struct {\n    Width uom.Quantity[dim.Length]\n    Height uom.Quantity[dim.Length]\n}\n\nfunc (c Cylinder) Radius() uom.Quantity[dim.Length] {\n    // d / 2\n    return uom.E[dim.Length](c.Width).Div(uom.Scalar(2)).As(Meter)\n}\n\nfunc (c Cylinder) CrossSection() uom.Quantity[dim.Area] {\n    // r^2 * pi\n    return uom.E[dim.Area](c.Radius()).Pow(2).Mul(uom.Scalar(math.Pi)).As(SquareMeter)\n}\n\nfunc (c Cylinder) Volume() uom.Quantity[dim.Volume] {\n    // r^2 * pi * h\n    return uom.E[dim.Volume](c.Height).Mul(c.CrossSection()).As(CubicMeter)\n}\n\nfunc (c Cylinder) VolumeInSingleCall() uom.Quantity[dim.Volume] {\n    return uom.E[dim.Volume](c.Width).\n        Div(uom.Scalar(2)).\n        Pow(2).\n        Mul(uom.Scalar(math.Pi)).\n        Mul(c.Height).\n        As(CubicMeter)\n}\n\nfunc main() {\n    c := Cylinder{\n        Width: uom.Q(6, Inch),\n        Height: uom.Q(1, Meter)\n    }\n\n    fmt.Printf(\"Volume: %.2f gallons\\n\", uom.As(c.Volume(), Gallon))\n    fmt.Printf(\"Volume: %.2f m^3\\n\", uom.As(c.Volume(), CubicMeter))\n    fmt.Printf(\"Volume: %.2f in^3\\n\", uom.As(c.Volume(), CubicInch))\n}\n```\n\n---\n\n### Step 4: Fix Dimension Errors\nThis library takes the stance that a dimension error is not a valid program. I.E. dimension analysis error is a compile error or runtime panic.\n\nNo amount of error handling can recover adding 60 seconds to 5 miles. The author simply made a mistake and will need to be more careful in their implementation.\n\nWe're all human and this will happen. The outcome to measure is how quickly the issue can be identified and corrected.\n\n#### Compile Errors\nBest case scenario: the compiler tells you exactly where your error is:\n```go\nfunc main() {\n    c := Cylinder{\n        Width: uom.Q(6, Gallon), // Compile-time error; Can not use type uom.Quantity[dim.Volume] as type uom.Quantity[dim.Length]\n        Height: uom.Q(1, Meter)\n    }\n}\n```\n\n#### Import Runtime Errors\nNext best case scenario: there is a panic a package import time. \n```go\nvar (\n    CubicInch = uom.DeriveUnit[dim.Volume](\"in^3\", uom.U(Inch).Pow(3), uom.U(Second).Per()) // Import-time panic. Target dimension L^3; Term dimension L^4*T^-1\n)\n```\n\n#### Evaluation Runtime Errors\nThis is the worst case scenario: you asked an expression to evaluate something that does not make physical sense and you only find out about this when the specific unit of code is called.\n\nThis will panic with a message describing the illegal operation as well as calling stack when the unit was called.\n```go\nfunc GetVolume(l, w, h uom.Quantity[dim.Length]) uom.Quantity[dim.Volume] {\n    return uom.One[dim.Volume]().Mul(l, w, h, h).As(CubicMeter) // Runtime panic when called. Target dimension L^3; Expression dimension L^4\n}\n```\n\n## How Dimensions are Handled\nCrucially, dimensions are tied to Go types for compile-time enforcment.\n\nAt runtime the dimension vectors are encoded to simple int64 values which means comparison and operations generally happen in a single instruction.\n\nSee: https://www.cs.utexas.edu/~novak/units95.html and related works.\n\n---\n\n## Next:\n\n  1. Affine Conversions: Accomdates Celsius and Fahrenheit\n  2. Serialization: It should be trivial to add MarshalText/UnmarshalText to uom.Quantity[T Dimension]\n  3. Aliases: \"foot\", \"feet\", \"ft\"\n  4. Registry:\n\n---\n\n## Horizon\nWork that is further off, but has promise to be extremely useful.\n\n### Leverage the Existing Work of [QUDT](https://www.qudt.org/pages/HomePage.html)\n\nWrite a go:generate tool that converts the library of maintained dimension vectors and units directly to the `dim`/`si`/`usc` packages.\n\nThere is already a generally accept body that maintains this information. Use it.\n\n\n---\n\n## Headwinds in Go\nGo's strength is its simplicity. But this also means that any library will have to make some compromises in its implementation.\n\n1. **No operator overloading**  \n    Expressions like `distance / time` must call functions instead of leaning on the compiler for evaluation. Expressions can still be quite readable, but will never be as readable as they could be in c++ or rust for example.\n\n2. **Minimal type system**  \n    Go’s type system is intentionally lean. In practice this puts upper limits on what can be validated at compile time and forces more runtime checks.\n\n3. **No `constexpr` or useful `const`**  \n    Without compile-time evaluation of constants, defining large unit libraries means the many conversion factors are computed at runtime.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelledge%2Fuom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkelledge%2Fuom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelledge%2Fuom/lists"}