Skip to content

Commit 7729886

Browse files
Claude drafted no_std support (#94)
* Claude drafted no_std support * Rework Write and WriteBytes relationship * Tidy unused imports * Further error suppression * Restore cargo check for std
1 parent 89e47b5 commit 7729886

File tree

17 files changed

+249
-94
lines changed

17 files changed

+249
-94
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
with:
2525
toolchain: ${{ matrix.toolchain }}
2626
- name: Cargo check
27-
run: cargo test
27+
run: cargo check
2828
- name: Cargo test
2929
run: cargo test --workspace --all-targets
30+
- name: Verify no_std support
31+
run: cargo check -p test-no-std

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ repository = "https://github.com/frankmcsherry/columnar.git"
1313
license = "MIT"
1414

1515
[workspace]
16-
members = ["columnar_derive"]
16+
members = ["columnar_derive", "test-no-std"]
1717

1818
[dependencies]
1919
serde = { version = "1.0", optional = true, features = ["derive"] }
@@ -27,6 +27,8 @@ bincode = "1.3.3"
2727
serde_json = "1.0"
2828

2929
[features]
30+
default = ["std"]
31+
std = []
3032
serde = ["dep:serde", "columnar_derive/serde", "smallvec/serde"]
3133

3234
[[example]]

src/adts/art.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! This ADT represents an unordered collection of byte sequences as a tree.
44
//! Like a trie, the paths down the tree correspond to byte sequences, and
55
//! the membership of a byte sequence is determined by the a viable path.
6+
use alloc::boxed::Box;
67

78
/// An ART node exists in the context of a sequence of bytes, and indicates
89
/// the possible options based on the next byte in the sequence.

src/adts/tree.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! A `Tree<T>` is a node with a value of type `T` and a list of children.
44
//! `Trees<TC>` stores a collection of trees with columnar storage for node values.
5+
use alloc::{vec::Vec, string::String};
56

67
use crate::{Borrow, Index, IndexAs, Len, Clear, Push};
78

@@ -173,7 +174,7 @@ impl<TC: Clear> Clear for Trees<TC> {
173174
impl<TC: Len> Trees<TC> {
174175
/// Pushes a tree into the container, storing nodes in BFS order.
175176
pub fn push_tree<T>(&mut self, tree: Tree<T>) where TC: for<'a> Push<&'a T> {
176-
let mut todo = std::collections::VecDeque::default();
177+
let mut todo = alloc::collections::VecDeque::default();
177178
todo.push_back(tree);
178179
while let Some(node) = todo.pop_front() {
179180
let cursor = self.values.len() + todo.len() + 1;
@@ -240,6 +241,7 @@ mod louds {
240241
#[cfg(test)]
241242
mod test {
242243

244+
use alloc::{vec, vec::Vec, string::ToString};
243245
use crate::common::{Index, Len, Clear};
244246
use crate::{Borrow, AsBytes, FromBytes};
245247
use super::{Tree, Trees};

src/arc.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Implementations of traits for `Arc<T>`
2-
use std::sync::Arc;
2+
use alloc::sync::Arc;
33

44
use crate::{Len, Borrow, AsBytes, FromBytes};
55

@@ -24,7 +24,8 @@ impl<'a, T: FromBytes<'a>> FromBytes<'a> for Arc<T> {
2424

2525
#[cfg(test)]
2626
mod tests {
27-
use std::sync::Arc;
27+
use alloc::sync::Arc;
28+
use alloc::{vec, vec::Vec};
2829
use crate::{Borrow, Len, AsBytes, FromBytes};
2930

3031
#[test]

src/boxed.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//!
66
//! We need this wrapper to distinguish which [`Push`] implementation to use, otherwise
77
//! the implementations would conflict.
8+
use alloc::boxed::Box;
89

910
use crate::{AsBytes, Borrow, Clear, Columnar, Container, FromBytes, Index, IndexMut, Len, Push, Ref};
1011

@@ -18,11 +19,11 @@ impl<T: Columnar> Columnar for Box<T> {
1819
#[derive(Copy, Clone, Default)]
1920
pub struct Boxed<T>(pub T);
2021

21-
impl<T> std::ops::Deref for Boxed<T> {
22+
impl<T> core::ops::Deref for Boxed<T> {
2223
type Target = T;
2324
#[inline(always)] fn deref(&self) -> &T { &self.0 }
2425
}
25-
impl<T> std::ops::DerefMut for Boxed<T> {
26+
impl<T> core::ops::DerefMut for Boxed<T> {
2627
#[inline(always)] fn deref_mut(&mut self) -> &mut T { &mut self.0 }
2728
}
2829
impl<C: Borrow> Borrow for Boxed<C> {
@@ -33,7 +34,7 @@ impl<C: Borrow> Borrow for Boxed<C> {
3334
#[inline(always)] fn reborrow_ref<'b, 'a: 'b>(item: Self::Ref<'a>) -> Self::Ref<'b> where Self: 'a { Boxed(C::reborrow_ref(item.0)) }
3435
}
3536
impl<C: Container> Container for Boxed<C> {
36-
#[inline(always)] fn extend_from_self(&mut self, other: Self::Borrowed<'_>, range: std::ops::Range<usize>) { self.0.extend_from_self(other.0, range) }
37+
#[inline(always)] fn extend_from_self(&mut self, other: Self::Borrowed<'_>, range: core::ops::Range<usize>) { self.0.extend_from_self(other.0, range) }
3738
#[inline(always)] fn reserve_for<'a, I>(&mut self, selves: I) where Self: 'a, I: Iterator<Item = Self::Borrowed<'a>> + Clone { self.0.reserve_for(selves.map(|x| x.0)) }
3839
}
3940
impl<C: Len> Len for Boxed<C> {

src/bytes.rs

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,37 @@
99
//! which can be formed from any type that implements `Deref<Target=[u8]>`. Doing so will check
1010
//! `u64` alignment, copy the contents if misaligned, and perform some structural validation.
1111
12+
/// A trait for writing bytes, usable in `no_std` environments.
13+
///
14+
/// This replaces `std::io::Write` for the columnar encoding functions.
15+
/// Implementations exist for `Vec<u8>` (always) and `std::io::Write` (with the `std` feature).
16+
pub trait WriteBytes {
17+
/// The error type returned by write operations.
18+
type Error;
19+
/// Write all bytes from the slice, or return an error.
20+
fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
21+
}
22+
23+
#[cfg(feature = "std")]
24+
impl<W: std::io::Write> WriteBytes for W {
25+
type Error = std::io::Error;
26+
#[inline(always)]
27+
fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
28+
std::io::Write::write_all(self, bytes)
29+
}
30+
}
31+
32+
#[cfg(not(feature = "std"))]
33+
impl WriteBytes for alloc::vec::Vec<u8> {
34+
type Error = core::convert::Infallible;
35+
#[inline(always)]
36+
fn write_all(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
37+
self.extend_from_slice(bytes);
38+
Ok(())
39+
}
40+
}
41+
42+
1243
/// A binary encoding of sequences of byte slices.
1344
///
1445
/// The encoding starts with a sequence of n+1 offsets describing where to find the n slices in the bytes that follow.
@@ -17,6 +48,7 @@
1748
/// This means that slices that are not multiples of eight bytes may leave unread bytes at their end, which is fine.
1849
pub mod indexed {
1950

51+
use alloc::{vec::Vec, string::String};
2052
use crate::AsBytes;
2153

2254
/// Encoded length in number of `u64` words required.
@@ -44,7 +76,7 @@ pub mod indexed {
4476
// Read 1: Number of offsets we will record, equal to the number of slices plus one.
4577
// TODO: right-size `store` before first call to `push`.
4678
let offsets = 1 + iter.as_bytes().count();
47-
let offsets_end: u64 = TryInto::<u64>::try_into((offsets) * std::mem::size_of::<u64>()).unwrap();
79+
let offsets_end: u64 = TryInto::<u64>::try_into((offsets) * core::mem::size_of::<u64>()).unwrap();
4880
store.push(offsets_end);
4981
// Read 2: Establish each of the offsets based on lengths of byte slices.
5082
let mut position_bytes = offsets_end;
@@ -73,7 +105,7 @@ pub mod indexed {
73105
let remaining_bytes = &bytes[whole_words..];
74106
if !remaining_bytes.is_empty() {
75107
let mut remainder = 0u64;
76-
let transmute: &mut [u8] = bytemuck::try_cast_slice_mut(std::slice::from_mut(&mut remainder)).expect("&[u64] should convert to &[u8]");
108+
let transmute: &mut [u8] = bytemuck::try_cast_slice_mut(core::slice::from_mut(&mut remainder)).expect("&[u64] should convert to &[u8]");
77109
for (i, byte) in remaining_bytes.iter().enumerate() {
78110
transmute[i] = *byte;
79111
}
@@ -82,22 +114,22 @@ pub mod indexed {
82114
}
83115
}
84116

85-
pub fn write<'a, A, W>(mut writer: W, iter: &A) -> std::io::Result<()>
117+
pub fn write<'a, A, W>(writer: &mut W, iter: &A) -> Result<(), W::Error>
86118
where
87119
A: AsBytes<'a>,
88-
W: std::io::Write,
120+
W: super::WriteBytes,
89121
{
90122
// Read 1: Number of offsets we will record, equal to the number of slices plus one.
91123
let offsets = 1 + iter.as_bytes().count();
92-
let offsets_end: u64 = TryInto::<u64>::try_into((offsets) * std::mem::size_of::<u64>()).unwrap();
93-
writer.write_all(bytemuck::cast_slice(std::slice::from_ref(&offsets_end)))?;
124+
let offsets_end: u64 = TryInto::<u64>::try_into((offsets) * core::mem::size_of::<u64>()).unwrap();
125+
writer.write_all(bytemuck::cast_slice(core::slice::from_ref(&offsets_end)))?;
94126
// Read 2: Establish each of the offsets based on lengths of byte slices.
95127
let mut position_bytes = offsets_end;
96128
for (align, bytes) in iter.as_bytes() {
97129
assert!(align <= 8);
98130
// Write length in bytes, but round up to words before updating `position_bytes`.
99131
let to_push: u64 = position_bytes + TryInto::<u64>::try_into(bytes.len()).unwrap();
100-
writer.write_all(bytemuck::cast_slice(std::slice::from_ref(&to_push)))?;
132+
writer.write_all(bytemuck::cast_slice(core::slice::from_ref(&to_push)))?;
101133
let round_len: u64 = ((bytes.len() + 7) & !7).try_into().unwrap();
102134
position_bytes += round_len;
103135
}
@@ -256,6 +288,7 @@ pub mod indexed {
256288
#[cfg(test)]
257289
mod test {
258290

291+
use alloc::{vec, vec::Vec, string::String};
259292
use crate::{Borrow, ContainerOf};
260293
use crate::common::Push;
261294
use crate::AsBytes;
@@ -316,6 +349,7 @@ pub mod indexed {
316349
/// A container of either typed columns, or serialized bytes that can be borrowed as the former.
317350
pub mod stash {
318351

352+
use alloc::{vec::Vec, string::String, boxed::Box};
319353
use crate::{Len, FromBytes};
320354
/// A container of either typed columns, or serialized bytes that can be borrowed as the former.
321355
///
@@ -351,7 +385,7 @@ pub mod stash {
351385

352386
impl<C: Default, B> Default for Stash<C, B> { fn default() -> Self { Self::Typed(Default::default()) } }
353387

354-
impl<C: crate::ContainerBytes, B: std::ops::Deref<Target = [u8]>> Stash<C, B> {
388+
impl<C: crate::ContainerBytes, B: core::ops::Deref<Target = [u8]>> Stash<C, B> {
355389
/// An analogue of `TryFrom` for any `B: Deref<Target=[u8]>`, avoiding coherence issues.
356390
///
357391
/// This is the recommended way to form a `Stash`, as it performs certain structural validation
@@ -407,7 +441,7 @@ pub mod stash {
407441
}
408442
}
409443

410-
impl<C: crate::ContainerBytes, B: std::ops::Deref<Target=[u8]> + Clone + 'static> crate::Borrow for Stash<C, B> {
444+
impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]> + Clone + 'static> crate::Borrow for Stash<C, B> {
411445

412446
type Ref<'a> = <C as crate::Borrow>::Ref<'a>;
413447
type Borrowed<'a> = <C as crate::Borrow>::Borrowed<'a>;
@@ -417,11 +451,11 @@ pub mod stash {
417451
#[inline(always)] fn reborrow_ref<'b, 'a: 'b>(item: Self::Ref<'a>) -> Self::Ref<'b> where Self: 'a { <C as crate::Borrow>::reborrow_ref(item) }
418452
}
419453

420-
impl<C: crate::ContainerBytes, B: std::ops::Deref<Target=[u8]>> Len for Stash<C, B> {
454+
impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Len for Stash<C, B> {
421455
#[inline(always)] fn len(&self) -> usize { self.borrow().len() }
422456
}
423457

424-
impl<C: crate::ContainerBytes, B: std::ops::Deref<Target=[u8]>> Stash<C, B> {
458+
impl<C: crate::ContainerBytes, B: core::ops::Deref<Target=[u8]>> Stash<C, B> {
425459
/// Borrows the contents, either from a typed container or by decoding serialized bytes.
426460
///
427461
/// This method is relatively cheap but is not free.
@@ -447,13 +481,14 @@ pub mod stash {
447481
Stash::Align(a) => 8 * a.len(),
448482
}
449483
}
450-
/// Write the contents into a `std::io::Write` using the [`indexed`] encoder.
451-
pub fn into_bytes<W: ::std::io::Write>(&self, writer: &mut W) {
484+
/// Write the contents into a [`WriteBytes`](crate::bytes::WriteBytes) destination.
485+
pub fn into_bytes<W: crate::bytes::WriteBytes>(&self, writer: &mut W) -> Result<(), W::Error> {
452486
match self {
453-
Stash::Typed(t) => { crate::bytes::indexed::write(writer, &t.borrow()).unwrap() },
454-
Stash::Bytes(b) => writer.write_all(&b[..]).unwrap(),
455-
Stash::Align(a) => writer.write_all(bytemuck::cast_slice(&a[..])).unwrap(),
487+
Stash::Typed(t) => { crate::bytes::indexed::write(writer, &t.borrow())?; },
488+
Stash::Bytes(b) => writer.write_all(&b[..])?,
489+
Stash::Align(a) => writer.write_all(bytemuck::cast_slice(&a[..]))?,
456490
}
491+
Ok(())
457492
}
458493
}
459494

@@ -481,6 +516,7 @@ pub mod stash {
481516
#[cfg(test)]
482517
mod test {
483518
use crate::ContainerOf;
519+
use alloc::{vec, vec::Vec, string::{String, ToString}};
484520

485521
#[test]
486522
fn round_trip() {

0 commit comments

Comments
 (0)