|
| 1 | +# Case Study: Issue #26 — Can we remove `num-traits` direct dependency from `platform-trees`? |
| 2 | + |
| 3 | +## Issue Summary |
| 4 | + |
| 5 | +**Title:** If num-traits already imported in platform-num, can we remove direct dependency safely to make it always the same as in platform-num? |
| 6 | + |
| 7 | +**URL:** https://github.com/linksplatform/trees-rs/issues/26 |
| 8 | + |
| 9 | +**Status:** Open (no comments) |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Timeline / Sequence of Events |
| 14 | + |
| 15 | +1. `platform-trees` was developed with both `num-traits` and `platform-num` as direct dependencies. |
| 16 | +2. `platform-num` v0.5.0 internally depends on `num-traits = "0.2.19"`. |
| 17 | +3. `platform-trees` declares `num-traits = "0.2"` (any 0.2.x), which currently resolves to `0.2.19`. |
| 18 | +4. Issue #26 was raised: is the direct `num-traits` dependency redundant, and can it be removed to ensure version consistency? |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## All Requirements from the Issue |
| 23 | + |
| 24 | +1. **Investigate**: Can we remove the direct `num-traits` dependency from `platform-trees`? |
| 25 | +2. **Safety**: Is the removal safe — won't it cause version mismatches? |
| 26 | +3. **Consistency**: Can we ensure `num-traits` version is always the same as what `platform-num` uses? |
| 27 | +4. **Deep case study**: Compile all data, reconstruct timeline, find root causes, propose solutions. |
| 28 | +5. **Check existing components/libraries** that solve similar problems. |
| 29 | +6. **If related to another repo**: File an issue there with reproducible example. |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +## Root Cause Analysis |
| 34 | + |
| 35 | +### What the code actually does |
| 36 | + |
| 37 | +`src/link_type.rs` directly imports two traits from `num-traits`: |
| 38 | + |
| 39 | +```rust |
| 40 | +use num_traits::{FromPrimitive, Unsigned}; |
| 41 | +``` |
| 42 | + |
| 43 | +These are used as trait bounds on `LinkType`: |
| 44 | + |
| 45 | +```rust |
| 46 | +pub trait LinkType: Number + Unsigned + Sized + TryFrom<u8> + FromPrimitive { ... } |
| 47 | +``` |
| 48 | + |
| 49 | +### Why direct removal fails |
| 50 | + |
| 51 | +**Rust requires explicit direct dependencies.** Transitive dependencies are not accessible in code unless the intermediate crate re-exports them. Attempting to remove `num-traits` from `Cargo.toml` causes: |
| 52 | + |
| 53 | +``` |
| 54 | +error[E0432]: unresolved import `num_traits` |
| 55 | + --> src/link_type.rs:1:5 |
| 56 | + | |
| 57 | +1 | use num_traits::{FromPrimitive, Unsigned}; |
| 58 | + | ^^^^^^^^^^ use of unresolved module or unlinked crate `num_traits` |
| 59 | +``` |
| 60 | + |
| 61 | +This is a fundamental Rust/Cargo design: each crate must explicitly declare its dependencies. |
| 62 | + |
| 63 | +### The version consistency risk |
| 64 | + |
| 65 | +Current state: |
| 66 | +- `platform-trees`: `num-traits = "0.2"` (any 0.2.x compatible) |
| 67 | +- `platform-num` v0.5.0: `num-traits = "0.2.19"` (exact 0.2.x) |
| 68 | + |
| 69 | +Currently Cargo resolves both to `0.2.19`, but: |
| 70 | +- If `platform-num` upgrades to `num-traits = "0.3"` in a future version, and `platform-trees` still declares `"0.2"`, they'd use **different incompatible versions** of `num-traits`. |
| 71 | +- In Rust, `num_traits_0_2::Unsigned` and `num_traits_0_3::Unsigned` are different types — code that combines them won't compile. |
| 72 | + |
| 73 | +### Does `platform-num` re-export `num-traits`? |
| 74 | + |
| 75 | +**No.** `platform-num`'s `src/lib.rs` exports only: |
| 76 | +```rust |
| 77 | +pub use imp::{LinkReference, MaxValue, Number, SignedNumber, ToSigned}; |
| 78 | +``` |
| 79 | + |
| 80 | +The `num-traits` traits (`Unsigned`, `FromPrimitive`, etc.) are used internally but not re-exported. |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +## Dependency Graph |
| 85 | + |
| 86 | +``` |
| 87 | +platform-trees v0.2.0 |
| 88 | +├── num-traits v0.2.19 ← DIRECT dependency (required) |
| 89 | +└── platform-num v0.5.0 |
| 90 | + └── num-traits v0.2.19 (*) ← same resolved version (currently) |
| 91 | +``` |
| 92 | + |
| 93 | +--- |
| 94 | + |
| 95 | +## Proposed Solutions |
| 96 | + |
| 97 | +### Solution A (Implemented): Pin `num-traits` to exact version used by `platform-num` |
| 98 | + |
| 99 | +Change `Cargo.toml` from: |
| 100 | +```toml |
| 101 | +num-traits = "0.2" |
| 102 | +``` |
| 103 | +to: |
| 104 | +```toml |
| 105 | +num-traits = "=0.2.19" |
| 106 | +``` |
| 107 | + |
| 108 | +**Pros:** Ensures version always matches `platform-num`. Simple change. |
| 109 | +**Cons:** Need to update this pin whenever `platform-num` upgrades `num-traits`. More restrictive for downstream users. |
| 110 | + |
| 111 | +### Solution B: Use `=` constraint matching `platform-num`'s constraint |
| 112 | + |
| 113 | +Change to: |
| 114 | +```toml |
| 115 | +num-traits = "0.2.19" |
| 116 | +``` |
| 117 | +(semver: >=0.2.19, <0.3.0) |
| 118 | + |
| 119 | +**Pros:** Stays flexible within 0.2.x while ensuring minimum compatible version. Best practice. |
| 120 | +**Cons:** Technically could resolve to a different minor bump than `platform-num`, but in practice SemVer guarantees compatibility within 0.2.x. |
| 121 | + |
| 122 | +### Solution C (Recommended for future): File issue on `platform-num` to re-export `num-traits` traits |
| 123 | + |
| 124 | +Ask `platform-num` maintainers to add: |
| 125 | +```rust |
| 126 | +pub use num_traits::{FromPrimitive, Unsigned, /* other used traits */}; |
| 127 | +``` |
| 128 | + |
| 129 | +This would allow `platform-trees` to remove `num-traits` entirely and use: |
| 130 | +```rust |
| 131 | +use platform_num::{Number, FromPrimitive, Unsigned}; |
| 132 | +``` |
| 133 | + |
| 134 | +**Pros:** True single-source-of-truth, guaranteed version consistency forever. |
| 135 | +**Cons:** Requires `platform-num` to expose those re-exports; changes its API surface; may require a semver bump. |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Implementation |
| 140 | + |
| 141 | +**Chosen approach (Solution B):** Update `platform-trees`'s `num-traits` constraint to match `platform-num`'s pinned version. This is the minimal, safe change that directly answers the issue's concern about version consistency. |
| 142 | + |
| 143 | +See: `Cargo.toml` line 22 — changed from `num-traits = "0.2"` to `num-traits = "0.2.19"`. |
| 144 | + |
| 145 | +**Filed issue on `platform-num`:** Requested re-export of `num-traits` traits to enable future removal of the direct dependency. See: https://github.com/linksplatform/Numbers/issues/141 |
| 146 | + |
| 147 | +--- |
| 148 | + |
| 149 | +## Similar Problems / Known Solutions |
| 150 | + |
| 151 | +- **`pub use` re-exports**: Standard Rust pattern — crates re-export their dependencies' public API to avoid requiring users to specify transitive deps. Example: `tokio` re-exports `bytes::Bytes`. |
| 152 | +- **Cargo's `[patch]` section**: Used to override transitive dep versions, but not applicable here. |
| 153 | +- **`extern crate` declarations**: Pre-2018 edition pattern, not relevant now. |
| 154 | +- **Facade crates**: A crate that bundles and re-exports multiple related crates (e.g., `serde` with its `derive` feature). Could be applied here if `platform-num` becomes a facade for the whole num-traits ecosystem. |
0 commit comments