Skip to content

Commit 3e090ee

Browse files
authored
initial docs, new_small & from_bits (#12)
* add `new_small` & `from_bits` * initial documentation
1 parent 1edc03e commit 3e090ee

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[![ci-badge][]][ci] [![docs-badge][]][docs] [![crates.io version]][crates.io link] [![rust-version-badge]][rust-version-link]
2+
3+
# smolbitset
4+
5+
A library for dynamically sized bitsets with small storage optimization.
6+
7+
All values up to `usize::MAX >> 1` are stored without incurring any heap allocations.\
8+
Any larger values dynamically allocate an appropriately sized `u32` slice on the heap.\
9+
`SmolBitSet` also has a niche optimization so `Option<SmolBitSet>` and `SmolBitSet` have the same size of 1 `usize`.
10+
11+
[ci]: https://github.com/serenity-rs/smolbitset/actions
12+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/serenity-rs/smolbitset/ci.yml?branch=main&style=flat-square
13+
[docs]: https://docs.rs/smolbitset
14+
[docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square
15+
[crates.io link]: https://crates.io/crates/smolbitset
16+
[crates.io version]: https://img.shields.io/crates/v/smolbitset.svg?style=flat-square
17+
[rust-version-badge]: https://img.shields.io/badge/rust-1.85.0+-93450a.svg?style=flat-square
18+
[rust-version-link]: https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html

src/lib.rs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
//! A library for dynamically sized bitsets with small storage optimization.
2+
//!
3+
//! All values up to `usize::MAX >> 1` are stored without incurring any heap allocations.\
4+
//! Any larger values dynamically allocate an appropriately sized `u32` slice on the heap.\
5+
//! [`SmolBitSet`] also has a niche optimization so [`Option<SmolBitSet>`] and [`SmolBitSet`] have the same size of 1 [`usize`].
6+
7+
#![doc(html_root_url = "https://docs.rs/smolbitset/*")]
18
#![allow(dead_code)]
29
#![cfg_attr(not(feature = "std"), no_std)]
310

@@ -23,7 +30,7 @@ use core::ptr::{self, NonNull};
2330

2431
/// Returns the index of the most significant bit set to 1 in the given data.
2532
///
26-
/// Note: the least significant bit is at index 1!
33+
/// The least significant bit is at index 1!
2734
macro_rules! highest_set_bit {
2835
($t:ty, $val:expr) => {
2936
(<$t>::BITS - $val.leading_zeros()) as usize
@@ -49,13 +56,95 @@ pub struct SmolBitSet {
4956
}
5057

5158
impl SmolBitSet {
59+
/// Creates a new empty [`SmolBitSet`].
60+
///
61+
/// # Examples
62+
///
63+
/// ```
64+
/// use smolbitset::SmolBitSet;
65+
///
66+
/// # #[allow(unused_mut)]
67+
/// let mut sbs = SmolBitSet::new();
68+
/// ```
5269
#[must_use]
5370
#[inline]
5471
pub const fn new() -> Self {
5572
let ptr = unsafe { NonNull::new_unchecked(ptr::without_provenance_mut(0b1)) };
73+
74+
// #![feature(nonnull_provenance)] -> https://github.com/rust-lang/rust/issues/135243
75+
// let ptr = NonNull::without_provenance(core::num::NonZero::<usize>::MIN);
76+
5677
Self { ptr }
5778
}
5879

80+
/// Creates a new [`SmolBitSet`] from the provided `val` without any heap allocation.
81+
///
82+
/// # Panics
83+
///
84+
/// Panics if the most significant bit in `val` is set to 1.
85+
///
86+
/// # Examples
87+
///
88+
/// ```
89+
/// use smolbitset::SmolBitSet;
90+
///
91+
/// const sbs: SmolBitSet = SmolBitSet::new_small(1234);
92+
/// assert_eq!(sbs, SmolBitSet::from(1234u16));
93+
/// ```
94+
#[must_use]
95+
pub const fn new_small(val: usize) -> Self {
96+
assert!(val.leading_zeros() >= 1, "the highest bit in val must be 0");
97+
98+
let mut res = Self::new();
99+
unsafe {
100+
res.write_inline_data_unchecked(val);
101+
}
102+
103+
res
104+
}
105+
106+
/// Creates a new [`SmolBitSet`] from the provided slice of `bits`.
107+
///
108+
/// # Examples
109+
///
110+
/// ```
111+
/// use smolbitset::SmolBitSet;
112+
///
113+
/// let sbs = SmolBitSet::from_bits(&[0, 4, 3, 6]);
114+
/// assert_eq!(sbs, SmolBitSet::from(0b0101_1001u8));
115+
/// # let sbs = SmolBitSet::from_bits(&[63]);
116+
/// # assert_eq!(sbs, SmolBitSet::from(1u64 << 63));
117+
/// ```
118+
#[must_use]
119+
pub fn from_bits(bits: &[usize]) -> Self {
120+
let Some(hb) = bits.iter().copied().max() else {
121+
return Self::new();
122+
};
123+
124+
let mut res = Self::new();
125+
res.ensure_capacity(hb + 1);
126+
127+
if res.is_inline() {
128+
let mut data = 0;
129+
130+
for &bit in bits {
131+
data |= 1 << bit;
132+
}
133+
134+
unsafe { res.write_inline_data_unchecked(data) }
135+
} else {
136+
let data = unsafe { res.as_slice_mut_unchecked() };
137+
138+
for &bit in bits {
139+
let s = bit % BST_BITS;
140+
let b = bit / BST_BITS;
141+
data[b] |= 1 << s;
142+
}
143+
}
144+
145+
res
146+
}
147+
59148
#[inline]
60149
fn is_inline(&self) -> bool {
61150
self.ptr.addr().get() & 0b1 == 1
@@ -119,6 +208,8 @@ impl SmolBitSet {
119208
unsafe { slice::from_raw_parts_mut(self.data_ptr_unchecked(), self.len_unchecked()) }
120209
}
121210

211+
/// # Warning
212+
/// `highest_bit` is 1 indexed, so the least significant bit is 1, not 0!
122213
#[inline]
123214
fn spill(&mut self, highest_bit: usize) {
124215
if !self.is_inline() {
@@ -130,6 +221,8 @@ impl SmolBitSet {
130221
}
131222
}
132223

224+
/// # Warning
225+
/// `highest_bit` is 1 indexed, so the least significant bit is 1, not 0!
133226
unsafe fn do_spill(&mut self, highest_bit: usize) {
134227
let len = highest_bit.div_ceil(BST_BITS);
135228
let len = core::cmp::max(len, INLINE_SLICE_PARTS);
@@ -160,6 +253,8 @@ impl SmolBitSet {
160253
self.ptr = unsafe { NonNull::new_unchecked(ptr.cast()) };
161254
}
162255

256+
/// # Warning
257+
/// `highest_bit` is 1 indexed, so the least significant bit is 1, not 0!
163258
#[inline]
164259
fn ensure_capacity(&mut self, highest_bit: usize) {
165260
if self.is_inline() {
@@ -180,6 +275,8 @@ impl SmolBitSet {
180275
}
181276
}
182277

278+
/// # Warning
279+
/// `highest_bit` is 1 indexed, so the least significant bit is 1, not 0!
183280
unsafe fn do_grow(&mut self, len: usize, highest_bit: usize) {
184281
// we need to grow our slice allocation
185282
let new_len = highest_bit.div_ceil(BST_BITS);
@@ -206,6 +303,9 @@ impl SmolBitSet {
206303
self.ptr = unsafe { NonNull::new_unchecked(new_ptr) };
207304
}
208305

306+
/// Returns the index of the most significant bit set to 1.
307+
///
308+
/// The least significant bit is at index 1!
209309
#[inline]
210310
fn highest_set_bit(&self) -> usize {
211311
if self.is_inline() {

0 commit comments

Comments
 (0)