Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@ All significant changes to this software be documented in this file.

## Unreleased

## v0.3.0 (2026-06-28)

### Breaking changes

* Renamed the old `BSize::with` mapping API to `BSize::map`.
* Renamed the generic `BSize<T>` wrapper to `ByteSize<T>`. `BSize` is now an alias for `ByteSize<usize>`.
* Renamed the `ByteSize` trait to `BaseByteSize`.
* Removed the `Displayable` trait. `BaseByteSize` now has a `to_f64` method that is the same as the `Displayable::canonicalize` method.
* Made the inner `ByteSize` field private. Use `ByteSize::b` to construct byte sizes and `ByteSize::bytes` to get the exact underlying byte count.

### New features

* Added `nightly` feature for using `ByteSize` with nightly-only features like `const_ops` and `const_trait_impl`.
* Added `BSize8`, `BSize16`, `BSize32`, and `BSize64` aliases.
* Added `BaseByteSize::to_f64` for converting supported byte size base types to approximate `f64` values.
* Added `BSize::as_b` for returning the byte count as an approximate `f64`.
* Added support for formatting positive infinity with `Display::new`, which acts as an overflow marker.
* Added `ByteSize::bytes` for returning the exact byte count as the underlying integer type.

## v0.2.1 (2026-06-27)

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ The [`bytesize`](https://crates.io/crates/bytesize) crate provides a `ByteSize`

I was more than happy to try `bytesize` at first. However, I found that it does not provide a way to specify the underlying integer type for the byte size. It uses `u64` internally, while most of the constants shown above are of type `usize`. This means that I have to convert between `u64` and `usize` frequently, which is not ideal. See [this issue](https://github.com/bytesize-rs/bytesize/issues/135) for more details.

What's more, to support calculations between byte size wrappers and numeric types, this crate implements `ByteSize::map` for producing a new wrapper, and exposes the `.0` field for arbitrary calculations from the underlying byte count. This avoids implementing arithmetic traits for calculations between byte size wrappers and numeric types. The latter would cause confusions like what result type should be used for `bytesize::ByteSize + u64`. However, `ByteSize` implements arithmetic traits for calculations between wrappers with the same base type, which is more intuitive and less error-prone.
What's more, to support calculations between byte size wrappers and numeric types, this crate implements `ByteSize::map` for producing a new wrapper, and exposes `ByteSize::bytes` for extracting the exact underlying byte count. This avoids implementing arithmetic traits for calculations between byte size wrappers and numeric types. The latter would cause confusions like what result type should be used for `bytesize::ByteSize + u64`. However, `ByteSize` implements arithmetic traits for calculations between wrappers with the same base type, which is more intuitive and less error-prone.

```rust
let result = bytesize::ByteSize::kib(4) + 64; // Is the result type bytesize::ByteSize or u64? Why?
let result = BSize64::kib(4).map(|b| b + 64); // Clearly the result type is BSize64.
let result = BSize64::kib(4).0 + 64; // Clearly the result type is u64.
let result = BSize64::kib(4).bytes() + 64; // Clearly the result type is u64.
```

There is no `Unit` as well. To obtain a constant for a specific unit, you can use `BSize64::kib(1).0` and this can be resolved at compile time.
There is no `Unit` as well. To obtain a constant for a specific unit, you can use `BSize64::kib(1).bytes()` and this can be resolved at compile time.

Finally, the following issues in `bytesize` have been resolved in this crate:

Expand Down
2 changes: 1 addition & 1 deletion bsize/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

[package]
name = "bsize"
version = "0.3.0-rc.2"
version = "0.3.0"

categories = ["development-tools", "no-std", "value-formatting"]
description = "Semantic wrappers and utilities for byte size representations."
Expand Down
16 changes: 8 additions & 8 deletions bsize/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl<T: BaseByteSize> ByteSize<T> {
///
/// See [`Display`] for examples.
pub fn display(&self) -> Display {
Display::new(self.0.to_f64())
Display::new(self.bytes().to_f64())
}
}

Expand Down Expand Up @@ -327,9 +327,9 @@ impl Display {
///
/// # Panics
///
/// Panics if the `size` is not finite or is negative.
/// Panics if the `size` is NaN or negative.
pub fn new(size: f64) -> Self {
assert!(size.is_finite() && size >= 0.0);
assert!(size >= 0.0, "size must be non-negative and not NaN");
let options = DisplayOptions::BINARY;
Self { size, options }
}
Expand Down Expand Up @@ -524,15 +524,15 @@ mod tests {
}

#[test]
#[should_panic]
fn test_new_rejects_nan_size() {
Display::new(f64::NAN);
fn test_formats_infinite_size() {
assert_snapshot!(Display::new(f64::INFINITY).binary(), @"inf EiB");
assert_snapshot!(Display::new(f64::INFINITY).decimal(), @"inf EB");
}

#[test]
#[should_panic]
fn test_new_rejects_infinite_size() {
Display::new(f64::INFINITY);
fn test_new_rejects_nan_size() {
Display::new(f64::NAN);
}

#[test]
Expand Down
82 changes: 38 additions & 44 deletions bsize/src/types/mod.rs → bsize/src/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,9 @@
use core::any::type_name;
use core::fmt;

use crate::ByteSize;
use crate::traits::BaseByteSize;

/// Byte size representation.
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ByteSize<T: BaseByteSize>(pub T);

/// Byte size representation backed by `usize`.
pub type BSize = ByteSize<usize>;

/// Byte size representation backed by `u8`.
pub type BSize8 = ByteSize<u8>;

/// Byte size representation backed by `u16`.
pub type BSize16 = ByteSize<u16>;

/// Byte size representation backed by `u32`.
pub type BSize32 = ByteSize<u32>;

/// Byte size representation backed by `u64`.
pub type BSize64 = ByteSize<u64>;

impl<T: BaseByteSize + fmt::Debug> fmt::Debug for ByteSize<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ByteSize<{}>({:?})", type_name::<T>(), self.0)
Expand All @@ -59,6 +41,12 @@ impl<T: BaseByteSize> ByteSize<T> {
pub const fn b(size: T) -> Self {
ByteSize(size)
}

/// Returns the exact byte count as the underlying integer type.
#[inline(always)]
pub const fn bytes(self) -> T {
self.0
}
}

#[cfg(feature = "nightly")]
Expand All @@ -68,12 +56,12 @@ mod stable;

#[cfg(test)]
mod tests {
use super::BSize;
use super::BSize8;
use super::BSize16;
use super::BSize32;
use super::BSize64;
use super::ByteSize;
use crate::BSize;
use crate::BSize8;
use crate::BSize16;
use crate::BSize32;
use crate::BSize64;
use crate::ByteSize;
use crate::assert_close;

#[test]
Expand Down Expand Up @@ -101,9 +89,15 @@ mod tests {
assert_eq!(BSize64::eib(2), ByteSize::<u64>::eib(2));
}

#[test]
fn returns_exact_bytes() {
const KIB: u64 = BSize64::kib(1).bytes();
assert_eq!(KIB, 1_024);
}

#[test]
fn constructs_u8_units() {
assert_eq!(BSize8::b(2).0, 2);
assert_eq!(BSize8::b(2).bytes(), 2);
}

#[test]
Expand All @@ -122,8 +116,8 @@ mod tests {

#[test]
fn constructs_u16_units() {
assert_eq!(BSize16::kb(2).0, 2_000);
assert_eq!(BSize16::kib(2).0, 2_048);
assert_eq!(BSize16::kb(2).bytes(), 2_000);
assert_eq!(BSize16::kib(2).bytes(), 2_048);
}

#[test]
Expand All @@ -144,8 +138,8 @@ mod tests {

#[test]
fn constructs_u32_units() {
assert_eq!(BSize32::gb(2).0, 2_000_000_000);
assert_eq!(BSize32::gib(2).0, 2_147_483_648);
assert_eq!(BSize32::gb(2).bytes(), 2_000_000_000);
assert_eq!(BSize32::gib(2).bytes(), 2_147_483_648);
}

#[test]
Expand All @@ -166,8 +160,8 @@ mod tests {

#[test]
fn constructs_u64_units() {
assert_eq!(BSize64::eb(2).0, 2_000_000_000_000_000_000);
assert_eq!(BSize64::eib(2).0, 2_305_843_009_213_693_952);
assert_eq!(BSize64::eb(2).bytes(), 2_000_000_000_000_000_000);
assert_eq!(BSize64::eib(2).bytes(), 2_305_843_009_213_693_952);
}

#[test]
Expand All @@ -178,38 +172,38 @@ mod tests {
#[cfg(target_pointer_width = "16")]
#[test]
fn returns_usize_units() {
assert_eq!(BSize::kb(2).0, 2_000);
assert_eq!(BSize::kib(2).0, 2_048);
assert_eq!(BSize::kb(2).bytes(), 2_000);
assert_eq!(BSize::kib(2).bytes(), 2_048);
assert_close(BSize::kb(2).as_kb(), 2.0);
assert_close(BSize::kib(2).as_kib(), 2.0);
}

#[cfg(target_pointer_width = "32")]
#[test]
fn returns_usize_units() {
assert_eq!(BSize::kb(2).0, 2_000);
assert_eq!(BSize::kib(2).0, 2_048);
assert_eq!(BSize::kb(2).bytes(), 2_000);
assert_eq!(BSize::kib(2).bytes(), 2_048);
assert_close(BSize::kb(2).as_kb(), 2.0);
assert_close(BSize::kib(2).as_kib(), 2.0);
assert_eq!(BSize::gb(2).0, 2_000_000_000);
assert_eq!(BSize::gib(2).0, 2_147_483_648);
assert_eq!(BSize::gb(2).bytes(), 2_000_000_000);
assert_eq!(BSize::gib(2).bytes(), 2_147_483_648);
assert_close(BSize::gb(2).as_gb(), 2.0);
assert_close(BSize::gib(2).as_gib(), 2.0);
}

#[cfg(target_pointer_width = "64")]
#[test]
fn returns_usize_units() {
assert_eq!(BSize::kb(2).0, 2_000);
assert_eq!(BSize::kib(2).0, 2_048);
assert_eq!(BSize::kb(2).bytes(), 2_000);
assert_eq!(BSize::kib(2).bytes(), 2_048);
assert_close(BSize::kb(2).as_kb(), 2.0);
assert_close(BSize::kib(2).as_kib(), 2.0);
assert_eq!(BSize::gb(2).0, 2_000_000_000);
assert_eq!(BSize::gib(2).0, 2_147_483_648);
assert_eq!(BSize::gb(2).bytes(), 2_000_000_000);
assert_eq!(BSize::gib(2).bytes(), 2_147_483_648);
assert_close(BSize::gb(2).as_gb(), 2.0);
assert_close(BSize::gib(2).as_gib(), 2.0);
assert_eq!(BSize::eb(2).0, 2_000_000_000_000_000_000);
assert_eq!(BSize::eib(2).0, 2_305_843_009_213_693_952);
assert_eq!(BSize::eb(2).bytes(), 2_000_000_000_000_000_000);
assert_eq!(BSize::eib(2).bytes(), 2_305_843_009_213_693_952);
assert_close(BSize::eb(2).as_eb(), 2.0);
assert_close(BSize::eib(2).as_eib(), 2.0);
}
Expand Down
3 changes: 2 additions & 1 deletion bsize/src/types/nightly.rs → bsize/src/impls/nightly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ impl<T: BaseByteSize> ByteSize<T> {
/// Returns byte count as bytes.
///
/// The result is approximate when the byte count cannot be represented
/// exactly as `f64`. Use `.0` for the exact underlying integer value.
/// exactly as `f64`. Use [`ByteSize::bytes`] for the exact underlying
/// integer value.
#[inline(always)]
pub const fn as_b(&self) -> f64
where
Expand Down
4 changes: 2 additions & 2 deletions bsize/src/types/stable.rs → bsize/src/impls/stable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] {
/// Returns byte count as bytes.
///
/// The result is approximate when the byte count cannot be
/// represented exactly as `f64`. Use `.0` for the exact underlying
/// integer value.
/// represented exactly as `f64`. Use [`ByteSize::bytes`] for the
/// exact underlying integer value.
#[inline(always)]
pub const fn as_b(&self) -> f64 {
self.0 as f64
Expand Down
34 changes: 25 additions & 9 deletions bsize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
//! assert!(BSize::kib(4) > BSize::kb(4));
//!
//! let size: BSize = BSize::b(4_096);
//! assert_eq!(size.0, 4_096);
//! assert_eq!(size.bytes(), 4_096);
//! ```
//!
//! Parse byte sizes from strings.
Expand Down Expand Up @@ -113,12 +113,12 @@
extern crate alloc;

mod display;
mod impls;
mod ops;
mod parse;
#[cfg(feature = "serde")]
mod serde;
mod traits;
mod types;

pub use self::display::Display;
pub use self::display::DisplayBaseUnit;
Expand All @@ -134,12 +134,28 @@ pub use self::traits::KiloByteSize;
pub use self::traits::MegaByteSize;
pub use self::traits::PetaByteSize;
pub use self::traits::TeraByteSize;
pub use self::types::BSize;
pub use self::types::BSize8;
pub use self::types::BSize16;
pub use self::types::BSize32;
pub use self::types::BSize64;
pub use self::types::ByteSize;

/// Byte size representation.
///
/// Use [`ByteSize::b`] to construct a value from bytes and [`ByteSize::bytes`] to get
/// the exact underlying byte count.
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ByteSize<T: BaseByteSize>(T);

/// Byte size representation backed by `usize`.
pub type BSize = ByteSize<usize>;

/// Byte size representation backed by `u8`.
pub type BSize8 = ByteSize<u8>;

/// Byte size representation backed by `u16`.
pub type BSize16 = ByteSize<u16>;

/// Byte size representation backed by `u32`.
pub type BSize32 = ByteSize<u32>;

/// Byte size representation backed by `u64`.
pub type BSize64 = ByteSize<u64>;

#[cfg(test)]
fn assert_close(actual: f64, expected: f64) {
Expand All @@ -161,7 +177,7 @@ mod property_tests {

impl quickcheck::Arbitrary for ByteSize<u64> {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
Self(u64::arbitrary(g))
ByteSize::b(u64::arbitrary(g))
}
}

Expand Down
24 changes: 12 additions & 12 deletions bsize/src/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,33 @@ mod tests {

#[test]
fn adds_byte_sizes() {
assert_eq!((BSize8::b(3) + BSize8::b(5)).0, 8);
assert_eq!((BSize16::b(3) + BSize16::b(5)).0, 8);
assert_eq!((BSize32::b(3) + BSize32::b(5)).0, 8);
assert_eq!((BSize64::b(3) + BSize64::b(5)).0, 8);
assert_eq!((BSize::b(3) + BSize::b(5)).0, 8);
assert_eq!((BSize8::b(3) + BSize8::b(5)).bytes(), 8);
assert_eq!((BSize16::b(3) + BSize16::b(5)).bytes(), 8);
assert_eq!((BSize32::b(3) + BSize32::b(5)).bytes(), 8);
assert_eq!((BSize64::b(3) + BSize64::b(5)).bytes(), 8);
assert_eq!((BSize::b(3) + BSize::b(5)).bytes(), 8);
}

#[test]
fn add_assigns_byte_sizes() {
let mut size = BSize::b(3);
size += BSize::b(5);
assert_eq!(size.0, 8);
assert_eq!(size.bytes(), 8);
}

#[test]
fn subtracts_byte_sizes() {
assert_eq!((BSize8::b(8) - BSize8::b(5)).0, 3);
assert_eq!((BSize16::b(8) - BSize16::b(5)).0, 3);
assert_eq!((BSize32::b(8) - BSize32::b(5)).0, 3);
assert_eq!((BSize64::b(8) - BSize64::b(5)).0, 3);
assert_eq!((BSize::b(8) - BSize::b(5)).0, 3);
assert_eq!((BSize8::b(8) - BSize8::b(5)).bytes(), 3);
assert_eq!((BSize16::b(8) - BSize16::b(5)).bytes(), 3);
assert_eq!((BSize32::b(8) - BSize32::b(5)).bytes(), 3);
assert_eq!((BSize64::b(8) - BSize64::b(5)).bytes(), 3);
assert_eq!((BSize::b(8) - BSize::b(5)).bytes(), 3);
}

#[test]
fn sub_assigns_byte_sizes() {
let mut size = BSize::b(8);
size -= BSize::b(5);
assert_eq!(size.0, 3);
assert_eq!(size.bytes(), 3);
}
}
Loading