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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ All significant changes to this software be documented in this file.
### Breaking changes

* Renamed `BSize::with` to `BSize::map`. A new `BSize::with` method has been added that accepts a closure returning arbitrary type, allowing for mapping a `BSize` to any other type. This follows `LocalKey::with`'s naming conventions.
* Removed the `Displayable` trait. `ByteSize` now has a `to_f64` method that is the same as the `Displayable::canonicalize` method.

### New features

* Added `nightly` feature for using `BSize` with nightly-only features like `const_ops` and `const_trait_impl`.
* Added `ByteSize::to_f64` for converting supported byte size underlying types to approximate `f64` values.
* Added `BSize::as_b` for returning the byte count as an approximate `f64`.

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

Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This crate provides multiple semantic wrappers and utilities for byte size repre

## Features

* `#![no_std]`-capable, no dependencies, and uses no heap allocation.
* `#![no_std]`-capable, no heap allocation, and no runtime dependencies by default.
* `BSize` wrappers over `u8`, `u16`, `u32`, `u64`, and `usize` for representing byte sizes with different underlying types.
* `FromStr` impl for `BSize`, allowing for parsing string size representations like "1.5 KiB" and "521 TB".
* `Display` impl for `BSize`, allowing for formatting byte sizes as human-readable strings in both binary (e.g., "1.5 MiB") and decimal (e.g., "1.5 MB") styles.
Expand Down
1 change: 1 addition & 0 deletions bsize/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ nightly = []
serde = ["dep:serde_core"]

[dependencies]
macroweave = "0.1.0"
serde_core = { version = "1", default-features = false, optional = true }

[dev-dependencies]
Expand Down
10 changes: 5 additions & 5 deletions bsize/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ use core::fmt;
use core::fmt::Write as _;

use crate::BSize;
use crate::Displayable;
use crate::ByteSize;

/// Create a [`Display`] instance for displaying a byte size.
///
/// See [`Display`] for examples. Use [`Display::new`] when the byte count is already represented
/// as an `f64`.
pub fn display(size: impl Displayable) -> Display {
Display::new(size.canonicalize())
pub fn display(size: impl ByteSize) -> Display {
Display::new(size.to_f64())
}

impl<T: Displayable> BSize<T> {
impl<T: ByteSize> BSize<T> {
/// Returns a [`Display`] wrapper.
///
/// See [`Display`] for examples.
pub fn display(&self) -> Display {
Display::new(self.0.canonicalize())
Display::new(self.0.to_f64())
}
}

Expand Down
14 changes: 12 additions & 2 deletions bsize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
//!
//! # Features
//!
//! * `#![no_std]`-capable, no dependencies, and uses no heap allocation.
//! * `#![no_std]`-capable, no heap allocation, and no runtime dependencies by default.
//! * [`BSize`] wrappers over `u8`, `u16`, `u32`, `u64`, and `usize` for representing byte sizes
//! with different underlying types.
//! * `FromStr` impl for `BSize`, allowing for parsing string size representations like "1.5 KiB"
Expand Down Expand Up @@ -119,7 +119,6 @@ pub use self::display::DisplayUnitSystem;
pub use self::display::display;
pub use self::parse::ParseError;
pub use self::traits::ByteSize;
pub use self::traits::Displayable;
pub use self::traits::ExaByteSize;
pub use self::traits::GigaByteSize;
pub use self::traits::KiloByteSize;
Expand All @@ -128,6 +127,17 @@ pub use self::traits::PetaByteSize;
pub use self::traits::TeraByteSize;
pub use self::types::BSize;

#[cfg(test)]
fn assert_close(actual: f64, expected: f64) {
let delta = (actual - expected).abs();
let tolerance = f64::EPSILON;

assert!(
delta <= tolerance,
"actual: {actual}, expected: {expected}, delta: {delta}, tolerance: {tolerance}",
);
}

#[cfg(test)]
mod property_tests {
use alloc::string::String;
Expand Down
49 changes: 5 additions & 44 deletions bsize/src/ops.rs → bsize/src/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use core::ops;

use crate::types::BSize;

macro_rules! impl_ops {
($($ty:ty),* $(,)?) => {
$(
impl ops::Add<BSize<$ty>> for BSize<$ty> {
type Output = Self;

#[inline(always)]
fn add(self, rhs: BSize<$ty>) -> Self::Output {
BSize(self.0 + rhs.0)
}
}

impl ops::AddAssign<BSize<$ty>> for BSize<$ty> {
#[inline(always)]
fn add_assign(&mut self, rhs: BSize<$ty>) {
self.0 += rhs.0;
}
}

impl ops::Sub<BSize<$ty>> for BSize<$ty> {
type Output = Self;

#[inline(always)]
fn sub(self, rhs: BSize<$ty>) -> Self::Output {
BSize(self.0 - rhs.0)
}
}

impl ops::SubAssign<BSize<$ty>> for BSize<$ty> {
#[inline(always)]
fn sub_assign(&mut self, rhs: BSize<$ty>) {
self.0 -= rhs.0;
}
}
)*
};
}

impl_ops!(u8, u16, u32, u64, usize);
#[cfg(feature = "nightly")]
mod nightly;
#[cfg(not(feature = "nightly"))]
mod stable;

#[cfg(test)]
mod tests {
use super::BSize;
use crate::BSize;

#[test]
fn adds_byte_sizes() {
Expand Down
83 changes: 83 additions & 0 deletions bsize/src/ops/nightly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2026 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use core::ops;

use crate::traits::ByteSize;
use crate::types::BSize;

const impl<T> ops::Add<BSize<T>> for BSize<T>
where
T: [const] ByteSize + [const] ops::Add<Output = T>,
{
type Output = Self;

#[inline(always)]
fn add(self, rhs: BSize<T>) -> Self::Output {
BSize(self.0 + rhs.0)
}
}

const impl<T> ops::AddAssign<BSize<T>> for BSize<T>
where
T: [const] ByteSize + [const] ops::AddAssign,
{
#[inline(always)]
fn add_assign(&mut self, rhs: BSize<T>) {
self.0 += rhs.0;
}
}

const impl<T> ops::Sub<BSize<T>> for BSize<T>
where
T: [const] ByteSize + [const] ops::Sub<Output = T>,
{
type Output = Self;

#[inline(always)]
fn sub(self, rhs: BSize<T>) -> Self::Output {
BSize(self.0 - rhs.0)
}
}

const impl<T> ops::SubAssign<BSize<T>> for BSize<T>
where
T: [const] ByteSize + [const] ops::SubAssign,
{
#[inline(always)]
fn sub_assign(&mut self, rhs: BSize<T>) {
self.0 -= rhs.0;
}
}

#[cfg(test)]
mod tests {
use crate::BSize;

#[test]
fn arithmetic_is_const() {
const SUM: BSize<u64> = BSize::b(3_u64) + BSize::b(5_u64);
const DIFFERENCE: BSize<u64> = BSize::b(8_u64) - BSize::b(5_u64);
const ASSIGNED: BSize<u64> = {
let mut size = BSize::b(3_u64);
size += BSize::b(5_u64);
size -= BSize::b(2_u64);
size
};

assert_eq!(SUM, BSize::b(8));
assert_eq!(DIFFERENCE, BSize::b(3));
assert_eq!(ASSIGNED, BSize::b(6));
}
}
51 changes: 51 additions & 0 deletions bsize/src/ops/stable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2026 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use core::ops;

use crate::types::BSize;

macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] {
impl ops::Add<BSize<Ty>> for BSize<Ty> {
type Output = Self;

#[inline(always)]
fn add(self, rhs: BSize<Ty>) -> Self::Output {
BSize(self.0 + rhs.0)
}
}

impl ops::AddAssign<BSize<Ty>> for BSize<Ty> {
#[inline(always)]
fn add_assign(&mut self, rhs: BSize<Ty>) {
self.0 += rhs.0;
}
}

impl ops::Sub<BSize<Ty>> for BSize<Ty> {
type Output = Self;

#[inline(always)]
fn sub(self, rhs: BSize<Ty>) -> Self::Output {
BSize(self.0 - rhs.0)
}
}

impl ops::SubAssign<BSize<Ty>> for BSize<Ty> {
#[inline(always)]
fn sub_assign(&mut self, rhs: BSize<Ty>) {
self.0 -= rhs.0;
}
}
});
36 changes: 18 additions & 18 deletions bsize/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use core::convert::TryFrom;
use core::fmt;
use core::str::FromStr;

use crate::BSize;
use crate::ByteSize;

/// The error returned when parsing a byte size fails.
#[derive(Debug, Clone, Eq, PartialEq)]
Expand All @@ -41,26 +43,24 @@ impl fmt::Display for ParseError {

impl core::error::Error for ParseError {}

macro_rules! impl_from_str {
($($ty:ty),* $(,)?) => {
$(
impl FromStr for BSize<$ty> {
type Err = ParseError;
macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] {
impl FromStr for BSize<Ty> {
type Err = ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let size = parse_size(s.as_bytes())?;
if size <= <$ty>::MAX as u64 {
Ok(BSize(size as $ty))
} else {
Err(ParseError::Overflow)
}
}
}
)*
};
}
fn from_str(s: &str) -> Result<Self, Self::Err> {
bsize_from_u64(parse_size(s.as_bytes())?)
}
}
});

impl_from_str!(u8, u16, u32, u64, usize);
fn bsize_from_u64<T>(size: u64) -> Result<BSize<T>, ParseError>
where
T: ByteSize + TryFrom<u64>,
{
T::try_from(size)
.map(BSize)
.map_err(|_| ParseError::Overflow)
}

// This is derived from `parse-size` [1].
//
Expand Down
Loading
Loading