Reimplement 'Decode' for 'alloc::vec::Vec', 'alloc::string::String', 'CString', 'LinkedList', 'HashMap', and 'HashSet'; Reimplement 'DecodeBorrowed' for 'alloc::vec::Vec', 'alloc::string::String', and 'CString'; Update and add tests; Update readme; Clean up code; Implement 'From<Infallible>' for all error types; Update docs; Rework 'EnumEncodeError' and 'EnumDecodeError'; Add 'encode_char' benchmark; Implement 'Encode' and 'Decode' for 'OsStr', 'OsString', 'c_void', and 'BinaryHeap'; Remove 'never-type' feature flag; Rework 'PrimDiscriminant' as 'PrimRepr'; Add 'PrimDiscriminant' enumeration; Implement 'From<T: PrimRepr>' for 'PrimDiscriminant'; Implement 'PrimRepr' for 'u8', 'u16', 'u32', 'u64', 'u128', 'usize', 'i8', 'i16', 'i32', 'i64', 'i128', and 'isize'; Implement 'Debug', 'Display', 'Binary', 'Octal', 'LowerHex', 'UpperHex', 'LowerExp', and 'UpperExp' for 'PrimDiscriminant'; Implement 'Clone', 'Copy', 'Eq', 'PartialEq', and 'Hash' for 'PrimDiscriminant'; Implement 'Eq' and 'PartialEq' for **all** error types; Add 'as_slice' and 'as_ptr' methods to 'Input'; Implement 'AsRef<[u8]>' and 'Borrow<[u8]>' for 'Input'; Implement 'SizedEncode' for 'c_void'; Fix '<LinkedList as Encode>::Error'; Add 'new_unchecked' constructor to 'String' and 'Vec'; Rework 'from_utf8' and 'from_utf8_unchecked' in 'String'; Remove 'StringError'; Rework 'String' to make it trivially-destructable; Actually mark 'String::as_mut_str' with 'const'; Unimplement 'PartialOrd<{&str, alloc::string::String}>' for 'String'; Implement 'PartialEq<str>' for 'String'; Unimplement 'PartialOrd<{[T; M], &[T], alloc::vec::Vec<T>}>' for 'Vec<T, N>'; Remove 'is_full' method from 'String' and 'Vec'; Implement 'Copy' for 'String'; Implement 'PartialEq<{Self, [u8], &[u8]}>', 'Eq', and 'Debug' for 'Input'; Implement 'PartialEq<[U]>' for 'Vec<T, ..>'; Implement 'PartialEq<Vec<U, ..>>' for 'alloc::vec::Vec<T>'; Implement 'PartialEq<String>' for 'alloc::string::String'; Add 'is_char_boundary' and 'as_mut_bytes' methods to 'String'; Add doc aliases; Update lints; Fix atomics being imported from 'std';

This commit is contained in:
Gabriel Bjørnager Jensen 2025-01-14 21:35:18 +01:00
parent d43ed46b08
commit 12975eabf5
71 changed files with 2594 additions and 1822 deletions

View file

@ -3,6 +3,49 @@
This is the changelog of [Oct](https://crates.io/crates/oct/).
See `README.md` for more information.
## 0.16.0
* Reimplement `Decode` for `alloc::vec::Vec`, `alloc::string::String`, `CString`, `LinkedList`, `HashMap`, and `HashSet`
* Reimplement `DecodeBorrowed` for `alloc::vec::Vec`, `alloc::string::String`, and `CString`
* Update and add tests
* Update readme
* Clean up code
* Implement `From<Infallible>` for all error types
* Update docs
* Rework `EnumEncodeError` and `EnumDecodeError`
* Add `encode_char` benchmark
* Implement `Encode` and `Decode` for `OsStr`, `OsString`, `c_void`, and `BinaryHeap`
* Remove `never-type` feature flag
* Rework `PrimDiscriminant` as `PrimRepr`
* Add `PrimDiscriminant` enumeration
* Implement `From<T: PrimRepr>` for `PrimDiscriminant`
* Implement `PrimRepr` for `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, and `isize`
* Implement `Debug`, `Display`, `Binary`, `Octal`, `LowerHex`, `UpperHex`, `LowerExp`, and `UpperExp` for `PrimDiscriminant`
* Implement `Clone`, `Copy`, `Eq`, `PartialEq`, and `Hash` for `PrimDiscriminant`
* Implement `Eq` and `PartialEq` for **all** error types
* Add `as_slice` and `as_ptr` methods to `Input`
* Implement `AsRef<[u8]>` and `Borrow<[u8]>` for `Input`
* Implement `SizedEncode` for `c_void`
* Fix `<LinkedList as Encode>::Error`
* Add `new_unchecked` constructor to `String` and `Vec`
* Rework `from_utf8` and `from_utf8_unchecked` in `String`
* Remove `StringError`
* Rework `String` to make it trivially-destructable
* Actually mark `String::as_mut_str` with `const`
* Unimplement `PartialOrd<{&str, alloc::string::String}>` for `String`
* Implement `PartialEq<str>` for `String`
* Unimplement `PartialOrd<{[T; M], &[T], alloc::vec::Vec<T>}>` for `Vec<T, N>`
* Remove `is_full` method from `String` and `Vec`
* Implement `Copy` for `String`
* Implement `PartialEq<{Self, [u8], &[u8]}>`, `Eq`, and `Debug` for `Input`
* Implement `PartialEq<[U]>` for `Vec<T, ..>`
* Implement `PartialEq<Vec<U, ..>>` for `alloc::vec::Vec<T>`
* Implement `PartialEq<String>` for `alloc::string::String`
* Add `is_char_boundary` and `as_mut_bytes` methods to `String`
* Add doc aliases
* Update lints
* Fix atomics being imported from `std`
## 0.15.3
* Update readme

View file

@ -131,6 +131,8 @@ same_name_method = "forbid"
self_named_module_files = "forbid"
separated_literal_suffix = "warn"
single_char_pattern = "warn"
std_instead_of_alloc = "forbid"
std_instead_of_core = "forbid"
str_split_at_newline = "warn"
string_lit_as_bytes = "forbid"
string_lit_chars_any = "warn"

View file

@ -20,18 +20,19 @@ According to my runs on an AMD Ryzen 7 3700X with default settings, these benchm
| Benchmark | [Bincode] | [Borsh] | Oct | [Postcard] |
| :--------------------------------- | --------: | ------: | -----: | ---------: |
| `encode_u8` | 0.977 | 0.871 | 0.754 | 0.916 |
| `encode_u32` | 0.967 | 0.983 | 0.730 | 2.727 |
| `encode_u128` | 2.178 | 2.175 | 1.481 | 6.002 |
| `encode_u8` | 0.927 | 0.939 | 0.742 | 0.896 |
| `encode_u32` | 1.069 | 1.007 | 0.738 | 2.732 |
| `encode_u128` | 2.180 | 2.204 | 1.522 | 6.412 |
| `encode_char` | 2.474 | 1.261 | 0.817 | 2.480 |
| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.000 |
| `encode_struct_unnamed` | 1.206 | 1.168 | 0.805 | 2.356 |
| `encode_struct_named` | 3.021 | 1.532 | 0.952 | 3.013 |
| `encode_enum_unit` | 0.245 | 0.294 | 0.000 | 0.294 |
| `decode_u8` | 0.952 | 0.895 | 0.885 | 0.894 |
| `decode_non_zero_u8` | 1.215 | 1.250 | 1.229 | 1.232 |
| `decode_bool` | 1.204 | 1.224 | 1.126 | 1.176 |
| **Total time** &#8594; | 11.964 | 1 0.392 | 7.963 | 18.609 |
| **Total deviation (p.c.)** &#8594; | +50 | +31 | ±0 | +134 |
| `encode_struct_unnamed` | 1.245 | 1.146 | 0.834 | 2.378 |
| `encode_struct_named` | 3.037 | 1.541 | 0.961 | 3.014 |
| `encode_enum_unit` | 0.250 | 0.297 | 0.000 | 0.296 |
| `decode_u8` | 0.992 | 0.926 | 0.915 | 0.981 |
| `decode_non_zero_u8` | 1.218 | 1.215 | 1.225 | 1.238 |
| `decode_bool` | 1.064 | 1.088 | 1.046 | 1.080 |
| **Total time** &#8594; | 14.456 | 11.624 | 8.800 | 21.509 |
| **Total deviation (p.c.)** &#8594; | +64 | +32 | ±0 | +144 |
[Bincode]: https://crates.io/crates/bincode/
[Borsh]: https://crates.io/crates/borsh/
@ -45,11 +46,11 @@ Do feel free to conduct your own tests of Oct.
## Data model
Most primitives encode losslessly, with the main exceptions being `usize` and `isize`.
These are instead first cast as `u16` and `i16`, respectively, due to portability concerns (with respect to embedded systems).
Primitives encode losslessly by default, although [`usize`] and [`isize`] are the exception to this.
Due to their machine-dependent representation, these are truncated to the smallest subset of values guaranteed by Rust, with this equating to a cast to [`u16`] or [`i16`], respectively.
Numerical primitives in general encode as little endian (and **not** ["network order"](https://en.wikipedia.org/wiki/Endianness#Networking)).
It is recommended for implementors to follow this convention as well.
Numerical types in general (including `char`) are encoded as little endian (and **not** ["network order"](https://en.wikipedia.org/wiki/Endianness#Networking) as is the norm in TCP/UDP/IP).
It is recommended for implementors of custom types to adhere to this convention as well.
See specific types' implementations for notes on their data models.
@ -60,54 +61,10 @@ It may therefore be undesired to store encodings long-term.
This crate revolves around the `Encode` and `Decode` traits, both of which handle conversions to and from byte streams.
Many standard types come implemented with Oct, including most primitives as well as some standard library types such as `Option` and `Result`.
Some [features](#feature-flags) enable an extended set of implementations.
These traits are already implemented by Oct for a large set of the standard types, such as [`Option`] and [`Mutex`](std::sync::Mutex).
Some [features](#feature-flags) enable an extended set of implementations that are locked behind unstable feature gates or other crates.
It is recommended in most cases to simply derive these two traits for user-defined types, although this is only supported for enumerations and structures &ndash; not untagged unions.
When deriving, each field is *chained* according to declaration order:
```rust
use oct::decode::Decode;
use oct::encode::Encode;
use oct::slot::Slot;
#[derive(Debug, Decode, Encode, PartialEq)]
struct Ints {
value0: u8,
value1: u16,
value2: u32,
value3: u64,
value4: u128,
}
const VALUE: Ints = Ints {
value0: 0x00,
value1: 0x02_01,
value2: 0x06_05_04_03,
value3: 0x0E_0D_0C_0B_0A_09_08_07,
value4: 0x1E_1D_1C_1B_1A_19_18_17_16_15_14_13_12_11_10_0F,
};
let mut buf = Slot::with_capacity(0x100);
buf.write(VALUE).unwrap();
assert_eq!(buf.len(), 0x1F);
assert_eq!(
buf,
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E
].as_slice(),
);
assert_eq!(buf.read().unwrap(), VALUE);
```
The following is a more complete example of a UDP server/client for geographic data:
The following is an example of a UDP server/client for geographic data:
```rust
use oct::decode::Decode;
@ -118,8 +75,8 @@ use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::thread::spawn;
// City, region, etc.:
#[derive(Clone, Copy, Debug, Decode, Encode, Eq, PartialEq, SizedEncode)]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Decode, Encode, Eq, PartialEq, SizedEncode)]
enum Area {
AlQuds,
Byzantion,
@ -129,8 +86,8 @@ enum Area {
}
// Client-to-server message:
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
#[non_exhaustive]
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
enum Request {
AtmosphericHumidity { area: Area },
AtmosphericPressure { area: Area },
@ -151,8 +108,8 @@ enum Response {
struct Party {
pub socket: UdpSocket,
pub request_buf: Slot::<Request>,
pub response_buf: Slot::<Response>,
pub request_buf: Slot<Request>,
pub response_buf: Slot<Response>,
}
impl Party {
@ -222,13 +179,18 @@ Oct defines the following, default features:
* `proc-macro`: Pulls procedural macros from the [`oct-macros`](https://crates.io/crates/oct-macros/) crate
* `std`: Enables implementations for types `std`, e.g. `Mutex` and `RwLock`
The following features can additionally be enabled for support with nightly-only constructs:
* `f128`: Enable implementations for the [`f128`] type
* `f16`: Enable implementations for the [`f16`] type
## Documentation
Oct has its documentation written alongside its source code for use by `rustdoc`.
See [Docs.rs](https://docs.rs/oct/latest/oct/) for an on-line, rendered instance.
Currently, these docs make use of some unstable features for the sake of readability.
The nightly toolchain is therefore required when rendering them.
The nightly toolchain is therefore always required when rendering them or or running tests herein.
## Contribution

View file

@ -22,7 +22,7 @@
[package]
name = "oct-benchmarks"
version = "0.15.3"
version = "0.16.0"
edition = "2021"
description = "Oct benchmarks."
license = "MIT"
@ -32,7 +32,7 @@ readme.workspace = true
repository.workspace = true
[dependencies]
oct = { path = "../oct", version = "0.15.0", features = ["proc-macro"]}
oct = { path = "../oct", version = "0.16.0", features = ["proc-macro"]}
bincode = "1.3.0"
rand = "0.8.0"

View file

@ -20,10 +20,10 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use core::array;
use core::num::NonZero;
use rand::random;
use rand::distributions::{Distribution, Standard};
use std::array;
use std::num::NonZero;
use std::time::Instant;
use zerocopy::{Immutable, IntoBytes};
@ -43,7 +43,8 @@ macro_rules! benchmark {
postcard: $postcard_op:block$(,)?
}$(,)?)+
} => {{
use ::std::{concat, eprint, eprintln, stringify};
use ::core::{concat, stringify};
use ::std::{eprint, eprintln};
macro_rules! time {
{ $op: block } => {{
@ -371,6 +372,53 @@ fn main() {
}
}
encode_char: {
bincode: {
use bincode::serialize_into;
const ITEM_SIZE: usize = size_of::<char>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
serialize_into(&mut buf, &random::<char>()).unwrap();
}
}
borsh: {
const ITEM_SIZE: usize = size_of::<char>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
borsh::to_writer::<u32, _>(&mut buf, &random::<char>().into()).unwrap();
}
}
oct: {
use oct::encode::{Encode, Output, SizedEncode};
const ITEM_SIZE: usize = char::MAX_ENCODED_SIZE;
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice();
let mut stream = Output::new(&mut buf);
for _ in 0x0..VALUE_COUNT {
random::<char>().encode(&mut stream).unwrap();
}
}
postcard: {
const ITEM_SIZE: usize = size_of::<char>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
postcard::to_io(&random::<char>(), &mut buf).unwrap();
}
}
}
encode_struct_unit: {
bincode: {
use bincode::serialize_into;

View file

@ -8,7 +8,7 @@
[package]
name = "oct-macros"
version = "0.15.3"
version = "0.16.0"
edition = "2021"
description = "Octonary transcoder. Procedural macros."
documentation = "https://docs.rs/oct-macros/"

View file

@ -6,7 +6,7 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use std::borrow::Borrow;
use core::borrow::Borrow;
use proc_macro2::Span;
use syn::{Expr, Lit, LitInt, Variant};

View file

@ -6,10 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::fmt;
use core::fmt::{Debug, Formatter};
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::fmt;
use std::fmt::{Debug, Formatter};
use syn::{
GenericParam,
Generics,

View file

@ -20,11 +20,11 @@ use syn::{
};
pub fn impl_derive_macro<S, E>(
input: DeriveInput,
trait_path: Path,
r#unsafe_token: Option<Token![unsafe]>,
struct_body: S,
enum_body: E,
input: DeriveInput,
trait_path: Path,
unsafe_token: Option<Token![unsafe]>,
struct_body: S,
enum_body: E,
) -> TokenStream
where
S: FnOnce(DataStruct) -> TokenStream,
@ -47,7 +47,7 @@ where
enum_body(data, repr)
}
Data::Union(..) => panic!("unions cannot derive `{trait_name:?}`"),
Data::Union(..) => panic!("unions cannot derive `{trait_name}`"),
};
let generic_params = &input.generics.params;

View file

@ -8,9 +8,9 @@
use crate::{Discriminants, Repr};
use core::iter;
use proc_macro2::TokenStream;
use quote::quote;
use std::iter;
use syn::{DataEnum, Fields};
#[must_use]
@ -49,7 +49,7 @@ pub fn decode_enum(data: DataEnum, repr: Repr) -> TokenStream {
});
quote! {
type Error = ::oct::error::EnumDecodeError<#repr, ::oct::error::GenericDecodeError>;
type Error = ::oct::error::EnumDecodeError<#repr, <#repr as ::oct::decode::Decode>::Error, ::oct::error::GenericDecodeError>;
#[inline]
fn decode(stream: &mut ::oct::decode::Input) -> ::core::result::Result<Self, Self::Error> {
@ -62,10 +62,10 @@ pub fn decode_enum(data: DataEnum, repr: Repr) -> TokenStream {
let this = match discriminant {
#(#discriminants => #values,)*
value => return Result::Err(::oct::error::EnumDecodeError::UnassignedDiscriminant { value }),
value => return ::core::result::Result::Err(::oct::error::EnumDecodeError::UnassignedDiscriminant(value)),
};
Result::Ok(this)
::core::result::Result::Ok(this)
}
}
}

View file

@ -6,10 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::iter;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataStruct, Fields};
use std::iter;
#[must_use]
pub fn decode_struct(data: DataStruct) -> TokenStream {

View file

@ -53,7 +53,7 @@ pub fn encode_enum(data: DataEnum, repr: Repr) -> TokenStream {
});
quote! {
type Error = ::oct::error::EnumEncodeError<#repr, ::oct::error::GenericEncodeError>;
type Error = ::oct::error::EnumEncodeError<<#repr as ::oct::encode::Encode>::Error, ::oct::error::GenericEncodeError>;
#[allow(unreachable_patterns)]
#[inline]

View file

@ -39,7 +39,7 @@ pub fn encode_struct(data: DataStruct) -> TokenStream {
#[inline]
fn encode(&self, stream: &mut ::oct::encode::Output) -> ::core::result::Result<(), Self::Error> {
let #pattern = self;
let #pattern = *self;
#(
::oct::encode::Encode::encode(#captures, stream)

View file

@ -8,9 +8,9 @@
use crate::Repr;
use core::iter;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::iter;
use syn::DataEnum;
#[must_use]

View file

@ -6,9 +6,9 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::iter;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::iter;
use syn::{
Attribute,
Ident,

View file

@ -8,7 +8,7 @@
[package]
name = "oct"
version = "0.15.3"
version = "0.16.0"
edition = "2021"
rust-version = "1.83"
description = "Octonary transcoder."
@ -32,12 +32,11 @@ alloc = []
proc-macro = ["dep:oct-macros"]
std = ["alloc"]
f128 = []
f16 = []
never-type = []
f128 = []
f16 = []
[dependencies]
oct-macros = { path = "../oct-macros", version = "0.15.0", optional = true}
oct-macros = { path = "../oct-macros", version = "0.16.0", optional = true}
[lints]
workspace = true

View file

@ -7,7 +7,7 @@
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod tests;
mod test;
use crate::decode::{DecodeBorrowed, Input};
use crate::error::{
@ -20,6 +20,7 @@ use crate::error::{
use core::cell::{Cell, RefCell, UnsafeCell};
use core::convert::Infallible;
use core::ffi::c_void;
use core::marker::{PhantomData, PhantomPinned};
use core::mem::MaybeUninit;
use core::net::{
@ -44,14 +45,22 @@ use core::time::Duration;
#[cfg(feature = "alloc")]
use {
crate::error::Utf8Error,
alloc::borrow::{Cow, ToOwned},
alloc::boxed::Box,
alloc::collections::{BinaryHeap, LinkedList},
alloc::ffi::CString,
alloc::rc::Rc,
alloc::sync::Arc,
};
#[cfg(feature = "std")]
use {
core::hash::{BuildHasher, Hash},
std::collections::{HashMap, HashSet},
std::ffi::OsString,
std::sync::{Mutex, RwLock},
std::time::{SystemTime, UNIX_EPOCH},
};
@ -59,6 +68,7 @@ use {
// Should we require `Encode` for `Decode`?
/// Denotes a type capable of being decoded.
#[doc(alias("Deserialise", "Deserialize"))]
pub trait Decode: Sized {
/// The type returned in case of error.
type Error;
@ -109,7 +119,7 @@ impl<T: Decode, const N: usize> Decode for [T; N] {
// dropped automatically, so we can just forget
// about it from this point on. `transmute` cannot
// be used here, and `transmute_unchecked` is re-
// served for the greedy rustc devs. :(
// served for the greedy rustc devs. >:(
let this = unsafe { buf.as_ptr().cast::<[T; N]>().read() };
Ok(this)
}
@ -129,6 +139,20 @@ impl<T: Decode> Decode for Arc<T> {
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode + Ord> Decode for BinaryHeap<T> {
type Error = <alloc::vec::Vec<T> as Decode>::Error;
#[inline(always)]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let v = alloc::vec::Vec::decode(input)?;
let this = v.into();
Ok(this)
}
}
impl Decode for bool {
type Error = Infallible;
@ -145,7 +169,7 @@ impl Decode for bool {
}
impl<T: Decode> Decode for Bound<T> {
type Error = EnumDecodeError<u8, T::Error>;
type Error = EnumDecodeError<u8, <u8 as Decode>::Error, T::Error>;
#[inline(always)]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
@ -168,7 +192,7 @@ impl<T: Decode> Decode for Bound<T> {
0x2 => Self::Unbounded,
value => return Err(EnumDecodeError::UnassignedDiscriminant { value }),
value => return Err(EnumDecodeError::UnassignedDiscriminant(value)),
};
Ok(this)
@ -189,6 +213,30 @@ impl<T: Decode> Decode for Box<T> {
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl Decode for CString {
type Error = <alloc::vec::Vec<u8> as Decode>::Error;
/// Decodes a byte slice from the input.
///
/// This implementation will always allocate one more byte than specified by the slice for the null terminator.
/// Note that any null value already in the data will truncate the final string.
#[inline(always)]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(len) = usize::decode(input);
let mut v = alloc::vec![0x00; len + 0x1];
input.read_into(&mut v[..len]).unwrap();
// SAFETY: We have guaranteed that there is at
// least one null value. We also don't care if the
// string gets truncated.
let this = unsafe { Self::from_vec_with_nul_unchecked(v) };
Ok(this)
}
}
impl<T: Decode> Decode for Cell<T> {
type Error = T::Error;
@ -201,6 +249,15 @@ impl<T: Decode> Decode for Cell<T> {
}
}
impl Decode for c_void {
type Error = Infallible;
#[inline(always)]
fn decode(_input: &mut Input) -> Result<Self, Self::Error> {
panic!("cannot deserialise `c_void` as it cannot be constructed to begin with")
}
}
impl Decode for char {
type Error = CharDecodeError;
@ -280,28 +337,84 @@ impl Decode for f16 {
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<K, V, S, E> Decode for HashMap<K, V, S>
where
K: Decode<Error = E> + Eq + Hash,
V: Decode<Error = E>,
S: BuildHasher + Default,
{
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, E>>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(len) = Decode::decode(input);
let mut this = Self::with_capacity_and_hasher(len, Default::default());
for i in 0x0..len {
let key= Decode::decode(input)
.map_err(|e| CollectionDecodeError::BadItem(ItemDecodeError { index: i, error: e }))?;
let value = Decode::decode(input)
.map_err(|e| CollectionDecodeError::BadItem(ItemDecodeError { index: i, error: e }))?;
this.insert(key, value);
}
Result::Ok(this)
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<K, S> Decode for HashSet<K, S>
where
K: Decode + Eq + Hash,
S: BuildHasher + Default,
{
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, K::Error>>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(len) = Decode::decode(input);
let mut this = Self::with_capacity_and_hasher(len, Default::default());
for i in 0x0..len {
let key = Decode::decode(input)
.map_err(|e| CollectionDecodeError::BadItem(ItemDecodeError { index: i, error: e }) )?;
this.insert(key);
}
Result::Ok(this)
}
}
impl Decode for Infallible {
type Error = Self;
#[inline(always)]
fn decode(_input: &mut Input) -> Result<Self, Self::Error> {
panic!("cannot deserialise `Infallible` as it cannot be serialised to begin with")
panic!("cannot deserialise `Infallible` as it cannot be constructed to begin with")
}
}
impl Decode for IpAddr {
type Error = EnumDecodeError<u8, Infallible>;
type Error = EnumDecodeError<u8, <u8 as Decode>::Error, Infallible>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let discriminant = u8::decode(input)
.map_err(EnumDecodeError::InvalidDiscriminant)?;
let Ok(discriminant) = u8::decode(input);
let this = match discriminant {
0x4 => Self::V4(Decode::decode(input).unwrap()),
0x6 => Self::V6(Decode::decode(input).unwrap()),
value => return Err(EnumDecodeError::UnassignedDiscriminant { value })
value => return Err(EnumDecodeError::UnassignedDiscriminant(value))
};
Ok(this)
@ -343,6 +456,28 @@ impl Decode for isize {
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> Decode for LinkedList<T> {
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(len) = usize::decode(input);
let mut this = Self::new();
for i in 0x0..len {
let value = T::decode(input)
.map_err(|e| CollectionDecodeError::BadItem(ItemDecodeError { index: i, error: e }))?;
this.push_back(value);
}
Ok(this)
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<T: Decode> Decode for Mutex<T> {
@ -357,25 +492,13 @@ impl<T: Decode> Decode for Mutex<T> {
}
}
#[cfg(feature = "never-type")]
#[cfg_attr(doc, doc(cfg(feature = "never-type")))]
impl Decode for ! {
type Error = Infallible;
#[inline(always)]
fn decode(_input: &mut Input) -> Result<Self, Self::Error> {
panic!("cannot deserialise `!` as it cannot be serialised to begin with")
}
}
impl<T: Decode> Decode for Option<T> {
type Error = T::Error;
#[expect(clippy::if_then_some_else_none)] // ???
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let sign = bool::decode(input)
.map_err::<T::Error, _>(|_e| unreachable!())?;
let Ok(sign) = bool::decode(input);
let this = if sign {
Some(Decode::decode(input)?)
@ -387,6 +510,20 @@ impl<T: Decode> Decode for Option<T> {
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Decode for OsString {
type Error = <alloc::string::String as Decode>::Error;
#[inline(always)]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let s: alloc::string::String = Decode::decode(input)?;
let this = s.into();
Ok(this)
}
}
impl<T> Decode for PhantomData<T> {
type Error = Infallible;
@ -499,7 +636,7 @@ where
T: Decode<Error = Err>,
E: Decode<Error: Into<Err>>,
{
type Error = EnumDecodeError<bool, Err>;
type Error = EnumDecodeError<bool, <bool as Decode>::Error, Err>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
@ -550,7 +687,7 @@ impl<T: Decode> Decode for Saturating<T> {
}
impl Decode for SocketAddr {
type Error = EnumDecodeError<u8, Infallible>;
type Error = EnumDecodeError<u8, <u8 as Decode>::Error, Infallible>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
@ -560,7 +697,7 @@ impl Decode for SocketAddr {
0x4 => Ok(Self::V4(Decode::decode(input).unwrap())),
0x6 => Ok(Self::V6(Decode::decode(input).unwrap())),
value => Err(EnumDecodeError::UnassignedDiscriminant { value }),
value => Err(EnumDecodeError::UnassignedDiscriminant(value)),
}
}
}
@ -593,6 +730,33 @@ impl Decode for SocketAddrV6 {
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl Decode for alloc::string::String {
type Error = CollectionDecodeError<Infallible, Utf8Error>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(len) = Decode::decode(input);
let mut v = alloc::vec![0x00; len];
input.read_into(&mut v).unwrap();
match Self::from_utf8(v) {
Ok(s) => Ok(s),
Err(e) => {
let i = e.utf8_error().valid_up_to();
let c = e.as_bytes()[i];
Err(CollectionDecodeError::BadItem(
Utf8Error { value: c, index: i },
))
}
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Decode for SystemTime {
@ -647,6 +811,34 @@ impl Decode for usize {
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> Decode for alloc::vec::Vec<T> {
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(len) = Decode::decode(input);
let mut this = Self::with_capacity(len);
let buf = this.as_mut_ptr();
for i in 0x0..len {
let value = Decode::decode(input)
.map_err(|e| CollectionDecodeError::BadItem(ItemDecodeError { index: i, error: e }))?;
// SAFETY: Each index is within bounds (i.e. capac-
// ity).
unsafe { buf.add(i).write(value) };
}
// SAFETY: We have initialised the buffer.
unsafe { this.set_len(len); }
Ok(this)
}
}
impl<T: Decode> Decode for Wrapping<T> {
type Error = T::Error;
@ -892,65 +1084,65 @@ impl_non_zero!(usize);
impl_atomic! {
width: "8",
ty: bool,
atomic_ty: std::sync::atomic::AtomicBool,
atomic_ty: core::sync::atomic::AtomicBool,
}
impl_atomic! {
width: "16",
ty: i16,
atomic_ty: std::sync::atomic::AtomicI16,
atomic_ty: core::sync::atomic::AtomicI16,
}
impl_atomic! {
width: "32",
ty: i32,
atomic_ty: std::sync::atomic::AtomicI32,
atomic_ty: core::sync::atomic::AtomicI32,
}
impl_atomic! {
width: "64",
ty: i64,
atomic_ty: std::sync::atomic::AtomicI64,
atomic_ty: core::sync::atomic::AtomicI64,
}
impl_atomic! {
width: "8",
ty: i8,
atomic_ty: std::sync::atomic::AtomicI8,
atomic_ty: core::sync::atomic::AtomicI8,
}
impl_atomic! {
width: "ptr",
ty: isize,
atomic_ty: std::sync::atomic::AtomicIsize,
atomic_ty: core::sync::atomic::AtomicIsize,
}
impl_atomic! {
width: "16",
ty: u16,
atomic_ty: std::sync::atomic::AtomicU16,
atomic_ty: core::sync::atomic::AtomicU16,
}
impl_atomic! {
width: "32",
ty: u32,
atomic_ty: std::sync::atomic::AtomicU32,
atomic_ty: core::sync::atomic::AtomicU32,
}
impl_atomic! {
width: "64",
ty: u64,
atomic_ty: std::sync::atomic::AtomicU64,
atomic_ty: core::sync::atomic::AtomicU64,
}
impl_atomic! {
width: "8",
ty: u8,
atomic_ty: std::sync::atomic::AtomicU8,
atomic_ty: core::sync::atomic::AtomicU8,
}
impl_atomic! {
width: "ptr",
ty: usize,
atomic_ty: std::sync::atomic::AtomicUsize,
atomic_ty: core::sync::atomic::AtomicUsize,
}

View file

@ -0,0 +1,186 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::char;
use oct::decode::{Decode, Input};
use oct::encode::{Encode, SizedEncode};
use oct::error::EnumDecodeError;
use oct::string::String;
use oct::vec::Vec;
macro_rules! test {
{
$(
$ty:ty {
$($data:expr => $value:expr),+$(,)?
}$(,)?
)*
} => {{
$($({
let data: &[u8] = &$data;
let mut input = ::oct::decode::Input::new(data);
let left: ::core::result::Result<$ty, <$ty as ::oct::decode::Decode>::Error> = ::oct::decode::Decode::decode(&mut input);
let right: ::core::result::Result<$ty, <$ty as ::oct::decode::Decode>::Error> = $value;
::std::assert_eq!(left, right);
})*)*
}};
}
#[test]
fn test_decode() {
test! {
i8 {
[0x00] => Ok( 0x00),
[0x7F] => Ok( 0x7F),
[0x80] => Ok(-0x80),
[0xFF] => Ok(-0x01),
}
i16 {
[0x00, 0x00] => Ok( 0x0000),
[0xFF, 0x7F] => Ok( 0x7FFF),
[0x00, 0x80] => Ok(-0x8000),
[0xFF, 0xFF] => Ok(-0x0001),
}
i32 {
[0x00, 0x00, 0x00, 0x00] => Ok( 0x00000000),
[0xFF, 0xFF, 0xFF, 0x7F] => Ok( 0x7FFFFFFF),
[0x00, 0x00, 0x00, 0x80] => Ok(-0x80000000),
[0xFF, 0xFF, 0xFF, 0xFF] => Ok(-0x00000001),
}
i64 {
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => Ok( 0x0000000000000000),
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F] => Ok( 0x7FFFFFFFFFFFFFFF),
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80] => Ok(-0x8000000000000000),
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] => Ok(-0x0000000000000001),
}
u128 {
[
0x7F, 0x8F, 0x6F, 0x9F, 0x5F, 0xAF, 0x4F, 0xBF,
0x3F, 0xCF, 0x2F, 0xDF, 0x1F, 0xEF, 0x0F, 0xFF,
] => Ok(0xFF_0F_EF_1F_DF_2F_CF_3F_BF_4F_AF_5F_9F_6F_8F_7F),
}
char {
[0xFD, 0xFF, 0x00, 0x00] => Ok(char::REPLACEMENT_CHARACTER),
}
[char; 0x5] {
[
0xBB, 0x03, 0x00, 0x00, 0x91, 0x03, 0x00, 0x00,
0xBC, 0x03, 0x00, 0x00, 0x94, 0x03, 0x00, 0x00,
0xB1, 0x03, 0x00, 0x00,
] => Ok(['\u{03BB}', '\u{0391}', '\u{03BC}', '\u{0394}', '\u{03B1}']),
}
Option<()> {
[0x00] => Ok(None),
[0x01] => Ok(Some(())),
}
Result<(), i8> {
[0x00, 0x00] => Ok(Ok(())),
[0x01, 0x7F] => Ok(Err(i8::MAX)),
}
Vec<u16, 0x6> {
[0x02, 0x00, 0xBB, 0xAA, 0xDD, 0xCC] => Ok(Vec::new(&[0xAA_BB, 0xCC_DD]).unwrap()),
}
String<0x6> {
[0x06, 0x00, 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC] => Ok(String::new("\u{65E5}\u{672C}").unwrap()),
}
}
}
#[test]
#[should_panic]
fn test_decode_alloc_vec_long_len() {
let data = [
0xFF, 0xFF,
];
let mut stream = Input::new(&data);
let _ = <alloc::vec::Vec<u32> as Decode>::decode(&mut stream).unwrap();
}
#[test]
fn test_decode_derive() {
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
struct ProcExit {
exit_code: i32,
timestmap: u64,
}
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
struct NewByte(u8);
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
struct Unit;
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
enum UnitOrFields {
Unit,
Unnamed(i32),
Named { timestamp: u64 },
}
test! {
ProcExit {
[
0x01, 0x00, 0x00, 0x00, 0x00, 0xE1, 0x0B, 0x5E,
0x00, 0x00, 0x00, 0x00,
] => Ok(ProcExit { exit_code: 0x1, timestmap: 1577836800 }),
}
NewByte {
[0x80] => Ok(NewByte(0x80)),
}
Unit {
[] => Ok(Unit),
}
UnitOrFields {
[
0x00, 0x00,
] => Ok(UnitOrFields::Unit),
[
0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
] => Ok(UnitOrFields::Unnamed(-0x1)),
[
0x02, 0x00, 0x4C, 0xC8, 0xC5, 0x66, 0x00, 0x00,
0x00, 0x00,
] => Ok(UnitOrFields::Named { timestamp: 1724237900 }),
[
0x03, 0x00,
] => Err(EnumDecodeError::UnassignedDiscriminant(0x3)),
}
}
}
#[test]
fn test_decode_oct_vec_long_len() {
let data = [
0xFF, 0xFF,
];
let mut stream = Input::new(&data);
let _ = <oct::vec::Vec<u32, 0x0> as Decode>::decode(&mut stream).unwrap_err();
}

View file

@ -1,158 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use oct::decode::Decode;
use oct::encode::{Encode, SizedEncode};
use std::char;
macro_rules! test {
{
$(
$ty:ty {
$($data:expr => $value:expr),+$(,)?
}$(,)?
)*
} => {{
$($({
let data: &[u8] = &$data;
let mut input = ::oct::decode::Input::new(data);
let left: $ty = ::oct::decode::Decode::decode(&mut input).unwrap();
let right: $ty = $value;
::std::assert_eq!(left, right);
})*)*
}};
}
#[test]
fn test_decode() {
test! {
i8 {
[0x00] => 0x00,
[0x7F] => 0x7F,
[0x80] => -0x80,
[0xFF] => -0x01,
}
i16 {
[0x00, 0x00] => 0x0000,
[0xFF, 0x7F] => 0x7FFF,
[0x00, 0x80] => -0x8000,
[0xFF, 0xFF] => -0x0001,
}
i32 {
[0x00, 0x00, 0x00, 0x00] => 0x00000000,
[0xFF, 0xFF, 0xFF, 0x7F] => 0x7FFFFFFF,
[0x00, 0x00, 0x00, 0x80] => -0x80000000,
[0xFF, 0xFF, 0xFF, 0xFF] => -0x00000001,
}
i64 {
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => 0x0000000000000000,
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F] => 0x7FFFFFFFFFFFFFFF,
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80] => -0x8000000000000000,
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] => -0x0000000000000001,
}
u128 {
[
0x7F, 0x8F, 0x6F, 0x9F, 0x5F, 0xAF, 0x4F, 0xBF,
0x3F, 0xCF, 0x2F, 0xDF, 0x1F, 0xEF, 0x0F, 0xFF,
] => 0xFF_0F_EF_1F_DF_2F_CF_3F_BF_4F_AF_5F_9F_6F_8F_7F,
}
char {
[0xFD, 0xFF, 0x00, 0x00] => char::REPLACEMENT_CHARACTER,
}
[char; 0x5] {
[
0xBB, 0x03, 0x00, 0x00, 0x91, 0x03, 0x00, 0x00,
0xBC, 0x03, 0x00, 0x00, 0x94, 0x03, 0x00, 0x00,
0xB1, 0x03, 0x00, 0x00,
] => ['\u{03BB}', '\u{0391}', '\u{03BC}', '\u{0394}', '\u{03B1}'],
}
Option<()> {
[0x00] => None,
[0x01] => Some(()),
}
Result<(), i8> {
[0x00, 0x00] => Ok(()),
[0x01, 0x7F] => Err(i8::MAX),
}
// Lid<Vec<u16>, 0x6> {
// [0x02, 0x00, 0xBB, 0xAA, 0xDD, 0xCC] => Lid([0xAA_BB, 0xCC_DD].into()),
// }
// Lid<String, 0x6> {
// [0x06, 0x00, 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC] => Lid("\u{65E5}\u{672C}".into()),
// }
}
}
#[test]
fn test_decode_derive() {
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
struct ProcExit {
exit_code: i32,
timestmap: u64,
}
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
struct NewByte(u8);
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
struct Unit;
#[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
enum UnitOrFields {
Unit,
Unnamed(i32),
Named { timestamp: u64 },
}
test! {
ProcExit {
[
0x01, 0x00, 0x00, 0x00, 0x00, 0xE1, 0x0B, 0x5E,
0x00, 0x00, 0x00, 0x00,
] => ProcExit { exit_code: 0x1, timestmap: 1577836800 },
}
NewByte {
[0x80] => NewByte(0x80),
}
Unit {
[] => Unit,
}
UnitOrFields {
[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
] => UnitOrFields::Unit,
[
0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0x00,
] => UnitOrFields::Unnamed(-0x1),
[
0x02, 0x00, 0x4C, 0xC8, 0xC5, 0x66, 0x00, 0x00,
0x00, 0x00,
] => UnitOrFields::Named { timestamp: 1724237900 },
}
}
}

View file

@ -13,10 +13,15 @@ use core::borrow::Borrow;
#[cfg(feature = "alloc")]
use {
alloc::boxed::Box,
alloc::ffi::CString,
alloc::rc::Rc,
alloc::sync::Arc,
core::ffi::CStr,
};
#[cfg(feature = "std")]
use std::ffi::{OsStr, OsString};
/// Indicates a scheme relationship between borrowed and owned types.
///
/// Implementing this trait is specifically a promise that <code>&lt;Self as [Decode]&gt;::[decode](Decode::decode)</code> can handle any encoding of `B`.
@ -31,6 +36,7 @@ use {
/// [\[T\]]: slice
///
/// An alternative to using arrays would be to use the [`Vec`](crate::vec::Vec) type, which *does* use the same scheme.
#[doc(alias("DeserialiseBorrowed", "DeserializeBorrowed"))]
pub trait DecodeBorrowed<B: ?Sized>: Borrow<B> + Decode { }
impl<T: Decode> DecodeBorrowed<T> for T { }
@ -43,6 +49,22 @@ impl<T: Decode> DecodeBorrowed<T> for Arc<T> { }
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> DecodeBorrowed<T> for Box<T> { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl DecodeBorrowed<CStr> for CString { }
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl DecodeBorrowed<OsStr> for OsString { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> DecodeBorrowed<T> for Rc<T> { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl DecodeBorrowed<str> for alloc::string::String { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> DecodeBorrowed<[T]> for alloc::vec::Vec<T> { }

View file

@ -6,8 +6,13 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod test;
use crate::error::InputError;
use core::borrow::Borrow;
use core::fmt::{self, Debug, Formatter};
use core::ptr::copy_nonoverlapping;
use core::slice;
@ -109,4 +114,67 @@ impl<'a> Input<'a> {
pub const fn position(&self) -> usize {
self.pos
}
/// Gets a pointer to the next byte of the input stream.
#[inline(always)]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
unsafe { self.buf.as_ptr().add(self.position()) }
}
/// Gets a slice of the remaining bytes.
#[inline(always)]
#[must_use]
pub const fn as_slice(&self) -> &[u8] {
unsafe {
let ptr = self.as_ptr();
let len = self.remaining();
slice::from_raw_parts(ptr, len)
}
}
}
impl AsRef<[u8]> for Input<'_> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl Borrow<[u8]> for Input<'_> {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.as_slice()
}
}
impl Debug for Input<'_> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(self.as_slice(), f)
}
}
impl Eq for Input<'_> { }
impl PartialEq for Input<'_> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.as_slice() == other.as_slice()
}
}
impl PartialEq<[u8]> for Input<'_> {
#[inline(always)]
fn eq(&self, other: &[u8]) -> bool {
self.as_slice() == other
}
}
impl PartialEq<&[u8]> for Input<'_> {
#[inline(always)]
fn eq(&self, other: &&[u8]) -> bool {
self.as_slice() == *other
}
}

View file

@ -0,0 +1,54 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use oct::decode::Input;
use oct::error::InputError;
#[test]
fn test_decode_input() {
let buf = [0xFF, 0xFE, 0xFD, 0xFC];
let mut input = Input::new(&buf);
assert_eq!(input.capacity(), 0x4);
assert_eq!(input.position(), 0x0);
assert_eq!(input.remaining(), 0x4);
assert_eq!(input, [0xFF, 0xFE, 0xFD, 0xFC].as_slice());
assert_eq!(input.read(0x1), Ok([0xFF].as_slice()));
assert_eq!(input.capacity(), 0x4);
assert_eq!(input.position(), 0x1);
assert_eq!(input.remaining(), 0x3);
assert_eq!(input, [0xFE, 0xFD, 0xFC].as_slice());
assert_eq!(input.read(0x1), Ok([0xFE].as_slice()));
assert_eq!(input.capacity(), 0x4);
assert_eq!(input.position(), 0x2);
assert_eq!(input.remaining(), 0x2);
assert_eq!(input, [0xFD, 0xFC].as_slice());
assert_eq!(input.read(0x1), Ok([0xFD].as_slice()));
assert_eq!(input.capacity(), 0x4);
assert_eq!(input.position(), 0x3);
assert_eq!(input.remaining(), 0x1);
assert_eq!(input, [0xFC].as_slice());
assert_eq!(input.read(0x1), Ok([0xFC].as_slice()));
assert_eq!(input.capacity(), 0x4);
assert_eq!(input.position(), 0x4);
assert_eq!(input.remaining(), 0x0);
assert_eq!(input, [].as_slice());
assert_eq!(input.read(0x1), Err(InputError { capacity: 0x4, position: 0x4, count: 0x1 }));
assert_eq!(input.read(0x0), Ok([].as_slice()));
}

View file

@ -7,7 +7,7 @@
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod tests;
mod test;
use crate::encode::Output;
use crate::error::{
@ -17,11 +17,12 @@ use crate::error::{
ItemEncodeError,
RefCellEncodeError,
UsizeEncodeError,
Utf8Error,
};
use core::cell::{Cell, LazyCell, RefCell, UnsafeCell};
use core::convert::Infallible;
use core::ffi::CStr;
use core::ffi::{CStr, c_void};
use core::hash::BuildHasher;
use core::hint::unreachable_unchecked;
use core::marker::{PhantomData, PhantomPinned};
@ -49,7 +50,7 @@ use core::time::Duration;
use {
alloc::borrow::{Cow, ToOwned},
alloc::boxed::Box,
alloc::collections::LinkedList,
alloc::collections::{BinaryHeap, LinkedList},
alloc::ffi::CString,
alloc::string::String,
alloc::vec::Vec,
@ -61,15 +62,18 @@ use alloc::sync::Arc;
#[cfg(feature = "std")]
use {
core::str,
std::collections::{HashMap, HashSet},
std::ffi::{OsStr, OsString},
std::sync::{LazyLock, Mutex, RwLock},
std::time::{SystemTime, UNIX_EPOCH},
};
/// Denotes a type capable of being encoded.
///
/// It is recommended to simply derive this trait for custom types.
/// It can, of course, also just be manually implemented.
/// It is recommended to simply derive this trait for custom types (see the [`Encode`](derive@crate::encode::Encode) macro).
/// The trait can, of course, also just be manually implemented.
///
/// If all possible encodings have a known, maximum size, then the [`SizedEncode`](crate::encode::SizedEncode) trait should be considered as well.
///
@ -105,6 +109,7 @@ use {
/// }
/// }
/// ```
#[doc(alias("Serialise", "Serialize"))]
pub trait Encode {
/// The type returned in case of error.
type Error;
@ -202,6 +207,17 @@ impl<T: Encode + ?Sized> Encode for Arc<T> {
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Encode> Encode for BinaryHeap<T> {
type Error = <[T] as Encode>::Error;
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.as_slice().encode(output)
}
}
impl Encode for bool {
type Error = <u8 as Encode>::Error;
@ -248,6 +264,40 @@ impl<T: Encode + ?Sized> Encode for Box<T> {
}
}
impl Encode for CStr {
type Error = <[u8] as Encode>::Error;
/// Encodes the string identically to [a byte slice](slice) containing the string's byte values **excluding** the null terminator.
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.to_bytes().encode(output)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl Encode for CString {
type Error = <CStr as Encode>::Error;
/// See the the implementation of [`CStr`].
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.as_c_str().encode(output)
}
}
impl Encode for c_void {
type Error = Infallible;
#[inline(always)]
fn encode(&self, _output: &mut Output) -> Result<(), Self::Error> {
// NOTE: Contrary to `Infallible` and/or `!`,
// `c_void` *can* actually be constructed (although
// only by the the standard library).
unreachable!();
}
}
impl<T: Copy + Encode> Encode for Cell<T> {
type Error = T::Error;
@ -277,28 +327,6 @@ impl<T: Encode + ToOwned + ?Sized> Encode for Cow<'_, T> {
}
}
impl Encode for CStr {
type Error = <[u8] as Encode>::Error;
/// Encodes the string identically to [a byte slice](slice) containing the string's byte values **excluding** the null terminator.
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.to_bytes().encode(output)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl Encode for CString {
type Error = <CStr as Encode>::Error;
/// See the the implementation of [`CStr`].
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.as_c_str().encode(output)
}
}
impl Encode for Duration {
type Error = Infallible;
@ -395,7 +423,7 @@ impl Encode for Infallible {
}
impl Encode for IpAddr {
type Error = EnumEncodeError<u8, Infallible>;
type Error = EnumEncodeError<<u8 as Encode>::Error, Infallible>;
/// Encodes a the address with a preceding discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6).
///
@ -479,7 +507,7 @@ impl<T: Encode> Encode for LazyLock<T> {
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Encode> Encode for LinkedList<T> {
type Error = CollectionEncodeError<UsizeEncodeError, (usize, T::Error)>;
type Error = CollectionEncodeError<UsizeEncodeError, ItemEncodeError<usize, T::Error>>;
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
@ -491,7 +519,7 @@ impl<T: Encode> Encode for LinkedList<T> {
for (i, v) in self.iter().enumerate() {
v
.encode(output)
.map_err(|e| CollectionEncodeError::BadItem((i, e)))?;
.map_err(|e| CollectionEncodeError::BadItem(ItemEncodeError { index: i, error: e }))?;
}
Ok(())
@ -512,20 +540,6 @@ impl<T: Encode + ?Sized> Encode for Mutex<T> {
}
}
// Especially useful for `Result<T, !>`.
// **If** that is even needed, of course.
#[cfg(feature = "never-type")]
#[cfg_attr(doc, doc(cfg(feature = "never-type")))]
impl Encode for ! {
type Error = Infallible;
#[inline]
fn encode(&self, _output: &mut Output) -> Result<(), Self::Error> {
// SAFETY: `!` objects can never be constructed.
unsafe { unreachable_unchecked() }
}
}
impl<T: Encode> Encode for Option<T> {
type Error = T::Error;
@ -554,6 +568,59 @@ impl<T: Encode> Encode for Option<T> {
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Encode for OsStr {
type Error = CollectionEncodeError<UsizeEncodeError, Utf8Error>;
/// Encodes the OS-specific string as a normal string.
///
/// `OsStr` is losely defined by Rust as being superset of the standard, UTF-8 `str`.
///
/// # Errors
///
/// This implementation will yield an error if the string `self` contains any non-UTF-8 octets.
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
let data = self.as_encoded_bytes();
let s = match str::from_utf8(data) {
Ok(s) => s,
Err(e) => {
let i = e.valid_up_to();
let c = data[i];
return Err(
CollectionEncodeError::BadItem(
Utf8Error { value: c, index: i },
),
);
}
};
if let Err(CollectionEncodeError::BadLength(e)) = s.encode(output) {
Err(CollectionEncodeError::BadLength(e))
} else {
Ok(())
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Encode for OsString {
type Error = <OsStr as Encode>::Error;
/// Encodes the OS-specific string as a normal string.
///
/// See [`OsStr`]'s implementation of `Encode` for more information.
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.as_os_str().encode(output)
}
}
impl<T: ?Sized> Encode for PhantomData<T> {
type Error = Infallible;
@ -784,7 +851,7 @@ impl Encode for str {
impl Encode for String {
type Error = <str as Encode>::Error;
/// See [`str`].
/// See [`prim@str`].
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.as_str().encode(output)
@ -857,7 +924,7 @@ impl Encode for usize {
#[inline]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
let value = u16::try_from(*self)
.map_err(|_e| UsizeEncodeError(*self))?;
.map_err(|_| UsizeEncodeError(*self))?;
let Ok(_) = value.encode(output);
Ok(())
@ -952,7 +1019,7 @@ macro_rules! impl_atomic {
/// The atomic object itself is read with the [`Relaxed`](core::sync::atomic::Ordering) ordering scheme.
#[inline(always)]
fn encode(&self, output: &mut ::oct::encode::Output) -> ::core::result::Result<(), Self::Error> {
use ::std::sync::atomic::Ordering;
use ::core::sync::atomic::Ordering;
self.load(Ordering::Relaxed).encode(output)
}
@ -1101,65 +1168,65 @@ impl_non_zero!(usize);
impl_atomic! {
width: "8",
ty: bool,
atomic_ty: std::sync::atomic::AtomicBool,
atomic_ty: core::sync::atomic::AtomicBool,
}
impl_atomic! {
width: "16",
ty: i16,
atomic_ty: std::sync::atomic::AtomicI16,
atomic_ty: core::sync::atomic::AtomicI16,
}
impl_atomic! {
width: "32",
ty: i32,
atomic_ty: std::sync::atomic::AtomicI32,
atomic_ty: core::sync::atomic::AtomicI32,
}
impl_atomic! {
width: "64",
ty: i64,
atomic_ty: std::sync::atomic::AtomicI64,
atomic_ty: core::sync::atomic::AtomicI64,
}
impl_atomic! {
width: "8",
ty: i8,
atomic_ty: std::sync::atomic::AtomicI8,
atomic_ty: core::sync::atomic::AtomicI8,
}
impl_atomic! {
width: "ptr",
ty: isize,
atomic_ty: std::sync::atomic::AtomicIsize,
atomic_ty: core::sync::atomic::AtomicIsize,
}
impl_atomic! {
width: "16",
ty: u16,
atomic_ty: std::sync::atomic::AtomicU16,
atomic_ty: core::sync::atomic::AtomicU16,
}
impl_atomic! {
width: "32",
ty: u32,
atomic_ty: std::sync::atomic::AtomicU32,
atomic_ty: core::sync::atomic::AtomicU32,
}
impl_atomic! {
width: "64",
ty: u64,
atomic_ty: std::sync::atomic::AtomicU64,
atomic_ty: core::sync::atomic::AtomicU64,
}
impl_atomic! {
width: "8",
ty: u8,
atomic_ty: std::sync::atomic::AtomicU8,
atomic_ty: core::sync::atomic::AtomicU8,
}
impl_atomic! {
width: "ptr",
ty: usize,
atomic_ty: std::sync::atomic::AtomicUsize,
atomic_ty: core::sync::atomic::AtomicUsize,
}

View file

@ -0,0 +1,143 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::time::Duration;
use oct::encode::{Encode, SizedEncode};
use oct::error::UsizeEncodeError;
use oct::string::String as OctString;
use std::time::{SystemTime, UNIX_EPOCH};
macro_rules! test {
{
$(
$ty:ty {
$($value:expr => $data:expr),+$(,)?
}$(,)?
)*
} => {{
$($({
let mut buf = ::std::vec![0x00; <$ty as ::oct::encode::SizedEncode>::MAX_ENCODED_SIZE];
let mut output = ::oct::encode::Output::new(&mut buf);
let lhs: ::core::result::Result<&[u8], <$ty as ::oct::encode::Encode>::Error> = <$ty as ::oct::encode::Encode>::encode(&$value, &mut output).map(|_| ::core::borrow::Borrow::borrow(&output));
let rhs: ::core::result::Result<&[u8], <$ty as ::oct::encode::Encode>::Error> = $data;
::std::assert_eq!(lhs, rhs);
})*)*
}};
}
#[test]
fn test_encode() {
test! {
u8 {
0x00 => Ok(&[0x00]),
0xFF => Ok(&[0xFF]),
0x7F => Ok(&[0x7F]),
}
u16 {
0x0F_7E => Ok(&[0x7E, 0x0F]),
}
u32 {
0x00_2F_87_E7 => Ok(&[0xE7, 0x87, 0x2F, 0x00]),
}
u64 {
0xF3_37_CF_8B_DB_03_2B_39 => Ok(&[0x39, 0x2B, 0x03, 0xDB, 0x8B, 0xCF, 0x37, 0xF3]),
}
u128 {
0x45_A0_15_6A_36_77_17_8A_83_2E_3C_2C_84_10_58_1A => Ok(&[
0x1A, 0x58, 0x10, 0x84, 0x2C, 0x3C, 0x2E, 0x83,
0x8A, 0x17, 0x77, 0x36, 0x6A, 0x15, 0xA0, 0x45,
]),
}
usize {
0x1A4 => Ok(&[0xA4, 0x01]),
0x10000 => Err(UsizeEncodeError(0x10000)),
}
[char; 0x5] {
['\u{03B4}', '\u{0190}', '\u{03BB}', '\u{03A4}', '\u{03B1}'] => Ok(&[
0xB4, 0x03, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00,
0xBB, 0x03, 0x00, 0x00, 0xA4, 0x03, 0x00, 0x00,
0xB1, 0x03, 0x00, 0x00,
]),
}
OctString<0x1> {
OctString::new("A").unwrap() => Ok(&[0x01, 0x00, 0x41]),
}
OctString<0xA> {
OctString::new("l\u{00F8}gma\u{00F0}ur").unwrap() => Ok(&[
0x0A, 0x00, 0x6C, 0xC3, 0xB8, 0x67, 0x6D, 0x61,
0xC3, 0xB0, 0x75, 0x72,
])
}
Result<u16, char> {
Ok(0x4545) => Ok(&[0x00, 0x45, 0x45]),
Err(char::REPLACEMENT_CHARACTER) => Ok(&[0x01, 0xFD, 0xFF, 0x00, 0x00]),
}
Option<()> {
None => Ok(&[0x00]),
Some(()) => Ok(&[0x01]),
}
SystemTime {
UNIX_EPOCH => Ok(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
UNIX_EPOCH - Duration::from_secs(0x1) => Ok(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
UNIX_EPOCH + Duration::from_secs(0x1) => Ok(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
}
}
}
#[test]
fn test_encode_derive() {
#[derive(Encode, SizedEncode)]
#[repr(transparent)]
struct Foo(char);
#[derive(Encode, SizedEncode)]
#[repr(u8)]
enum Bar {
Unit = 0x45,
Pretty(bool) = 127,
Teacher { initials: [char; 0x3] },
}
test! {
Foo {
Foo('\u{FDF2}') => Ok(&[0xF2, 0xFD, 0x00, 0x00]),
}
Bar {
Bar::Unit => Ok(&[0x45]),
Bar::Pretty(true) => Ok(&[0x7F, 0x01]),
Bar::Teacher { initials: ['T', 'L', '\0'] } => Ok(&[
0x80, 0x54, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
]),
}
}
}

View file

@ -1,123 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use oct::encode::{Encode, SizedEncode};
use std::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
macro_rules! test {
{
$(
$ty:ty {
$($value:expr => $data:expr),+$(,)?
}$(,)?
)*
} => {{
$($({
let right: &[u8] = &$data;
let mut left = ::std::vec![0x00; right.len()];
let mut output = ::oct::encode::Output::new(&mut left);
<$ty as ::oct::encode::Encode>::encode(&$value, &mut output).unwrap();
::std::assert_eq!(left, right);
})*)*
}};
}
#[test]
fn test_encode() {
test! {
u8 {
0x00 => [0x00],
0xFF => [0xFF],
0x7F => [0x7F],
}
u16 {
0x0F_7E => [0x7E, 0x0F],
}
u32 {
0x00_2F_87_E7 => [0xE7, 0x87, 0x2F, 0x00],
}
u64 {
0xF3_37_CF_8B_DB_03_2B_39 => [0x39, 0x2B, 0x03, 0xDB, 0x8B, 0xCF, 0x37, 0xF3],
}
u128 {
0x45_A0_15_6A_36_77_17_8A_83_2E_3C_2C_84_10_58_1A => [
0x1A, 0x58, 0x10, 0x84, 0x2C, 0x3C, 0x2E, 0x83,
0x8A, 0x17, 0x77, 0x36, 0x6A, 0x15, 0xA0, 0x45,
],
}
[char; 0x5] {
['\u{03B4}', '\u{0190}', '\u{03BB}', '\u{03A4}', '\u{03B1}'] => [
0xB4, 0x03, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00,
0xBB, 0x03, 0x00, 0x00, 0xA4, 0x03, 0x00, 0x00,
0xB1, 0x03, 0x00, 0x00,
],
}
Result<u16, char> {
Ok(0x4545) => [0x00, 0x45, 0x45],
Err(char::REPLACEMENT_CHARACTER) => [0x01, 0xFD, 0xFF, 0x00, 0x00],
}
Option<()> {
None => [0x00],
Some(()) => [0x01],
}
SystemTime {
UNIX_EPOCH => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
UNIX_EPOCH - Duration::from_secs(0x1) => [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
UNIX_EPOCH + Duration::from_secs(0x1) => [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
}
}
}
#[test]
fn test_encode_derive() {
#[derive(Encode, SizedEncode)]
#[repr(transparent)]
struct Foo(char);
#[derive(Encode, SizedEncode)]
#[repr(u8)]
enum Bar {
Unit = 0x45,
Pretty(bool) = 127,
Teacher { initials: [char; 0x3] },
}
test! {
Foo {
Foo('\u{FDF2}') => [0xF2, 0xFD, 0x00, 0x00],
}
Bar {
Bar::Unit => [0x45],
Bar::Pretty(true) => [0x7F, 0x01],
Bar::Teacher { initials: ['T', 'L', '\0'] } => [
0x80, 0x54, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
],
}
}
}

View file

@ -85,6 +85,49 @@ use_mod!(pub sized_encode);
///
/// The [`Error`](Encode::Error) type will in all cases just be `GenericEncodeError`.
///
/// ## Example
///
/// ```rust
/// use oct::decode::Decode;
/// use oct::encode::Encode;
/// use oct::slot::Slot;
///
/// #[derive(Debug, Decode, Encode, PartialEq)]
/// struct Ints {
/// value0: u8,
/// value1: u16,
/// value2: u32,
/// value3: u64,
/// value4: u128,
/// }
///
/// const VALUE: Ints = Ints {
/// value0: 0x00,
/// value1: 0x02_01,
/// value2: 0x06_05_04_03,
/// value3: 0x0E_0D_0C_0B_0A_09_08_07,
/// value4: 0x1E_1D_1C_1B_1A_19_18_17_16_15_14_13_12_11_10_0F,
/// };
///
/// let mut buf = Slot::with_capacity(0x100);
///
/// buf.write(VALUE).unwrap();
///
/// assert_eq!(buf.len(), 0x1F);
///
/// assert_eq!(
/// buf,
/// [
/// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
/// 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
/// 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
/// 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E
/// ].as_slice(),
/// );
///
/// assert_eq!(buf.read().unwrap(), VALUE);
/// ```
///
/// # Enums
///
/// Enumerations encode like structures except that each variant additionally encodes a unique discriminant.
@ -93,6 +136,8 @@ use_mod!(pub sized_encode);
/// A custom discriminant may be set instead by assigning the variant an integer constant.
/// Unspecified discriminants then increment the previous variant's discriminant:
///
/// ## Example
///
/// ```rust
/// use oct::encode::Encode;
/// use oct::slot::Slot;
@ -138,6 +183,17 @@ use_mod!(pub sized_encode);
///
/// Unions cannot derive `Encode` due to the uncertainty of their contents.
/// The trait should therefore be implemented manually for such types.
///
/// ## Example
///
/// ```rust compile_fail
/// use oct::encode::Encode;
///
/// #[derive(Encode)]
/// union MyUnion {
/// my_field: u32,
/// }
/// ```
#[cfg(feature = "proc-macro")]
#[cfg_attr(doc, doc(cfg(feature = "proc-macro")))]
#[doc(inline)]

View file

@ -6,14 +6,17 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod test;
use crate::error::OutputError;
use core::borrow::Borrow;
use core::fmt::{self, Debug, Formatter};
use core::ptr::copy_nonoverlapping;
use core::slice;
/// Byte stream suitable for writing.
#[derive(Debug, Eq)]
pub struct Output<'a> {
buf: &'a mut [u8],
pos: usize,
@ -57,25 +60,6 @@ impl<'a> Output<'a> {
Ok(())
}
/// Gets a pointer to the first byte of the output stream.
#[inline(always)]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
self.buf.as_ptr()
}
/// Gets a slice of the written bytes in the output stream.
#[inline(always)]
#[must_use]
pub const fn as_slice(&self) -> &[u8] {
unsafe {
let ptr = self.as_ptr();
let len = self.position();
slice::from_raw_parts(ptr, len)
}
}
/// Retrieves the maximum capacity of the output stream.
#[inline(always)]
#[must_use]
@ -98,6 +82,25 @@ impl<'a> Output<'a> {
pub const fn position(&self) -> usize {
self.pos
}
/// Gets a pointer to the first byte of the output stream.
#[inline(always)]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
self.buf.as_ptr()
}
/// Gets a slice of the written bytes.
#[inline(always)]
#[must_use]
pub const fn as_slice(&self) -> &[u8] {
unsafe {
let ptr = self.as_ptr();
let len = self.position();
slice::from_raw_parts(ptr, len)
}
}
}
impl AsRef<[u8]> for Output<'_> {
@ -114,6 +117,15 @@ impl Borrow<[u8]> for Output<'_> {
}
}
impl Debug for Output<'_> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(self.as_slice(), f)
}
}
impl Eq for Output<'_> { }
impl PartialEq for Output<'_> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
@ -134,10 +146,3 @@ impl PartialEq<&[u8]> for Output<'_> {
self.as_slice() == *other
}
}
impl PartialEq<&mut [u8]> for Output<'_> {
#[inline(always)]
fn eq(&self, other: &&mut [u8]) -> bool {
self.as_slice() == *other
}
}

View file

@ -0,0 +1,54 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use oct::encode::Output;
use oct::error::OutputError;
#[test]
fn test_encode_output() {
let mut buf = [0x00; 0x4];
let mut output = Output::new(&mut buf);
assert_eq!(output.capacity(), 0x4);
assert_eq!(output.position(), 0x0);
assert_eq!(output.remaining(), 0x4);
assert_eq!(output, [].as_slice());
assert_eq!(output.write(&[0x04]), Ok(()));
assert_eq!(output.capacity(), 0x4);
assert_eq!(output.position(), 0x1);
assert_eq!(output.remaining(), 0x3);
assert_eq!(output, [0x04].as_slice());
assert_eq!(output.write(&[0x03]), Ok(()));
assert_eq!(output.capacity(), 0x4);
assert_eq!(output.position(), 0x2);
assert_eq!(output.remaining(), 0x2);
assert_eq!(output, [0x04, 0x03].as_slice());
assert_eq!(output.write(&[0x02]), Ok(()));
assert_eq!(output.capacity(), 0x4);
assert_eq!(output.position(), 0x3);
assert_eq!(output.remaining(), 0x1);
assert_eq!(output, [0x04, 0x03, 0x02].as_slice());
assert_eq!(output.write(&[0x01]), Ok(()));
assert_eq!(output.capacity(), 0x4);
assert_eq!(output.position(), 0x4);
assert_eq!(output.remaining(), 0x0);
assert_eq!(output, [0x04, 0x03, 0x02, 0x01].as_slice());
assert_eq!(output.write(&[0x00]), Err(OutputError { capacity: 0x4, position: 0x4, count: 0x1 }));
assert_eq!(output.write(&[]), Ok(()));
}

View file

@ -7,12 +7,13 @@
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod tests;
mod test;
use crate::encode::Encode;
use core::cell::{Cell, LazyCell, RefCell, UnsafeCell};
use core::convert::Infallible;
use core::ffi::c_void;
use core::marker::{PhantomData, PhantomPinned};
use core::net::{
IpAddr,
@ -60,6 +61,7 @@ use {
///
/// Also note that -- in practice -- this trait is **not** strictly enforceable.
/// Users of this trait should assume that it is mostly properly defined, but still with the possibility of it not being such.
#[doc(alias("SizedSerialise", "SizedSerialize"))]
pub trait SizedEncode: Encode {
/// The maximum, guaranteed amount of bytes that can result from an encoding.
///
@ -108,6 +110,10 @@ impl<T: SizedEncode + ?Sized> SizedEncode for Box<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
impl SizedEncode for c_void {
const MAX_ENCODED_SIZE: usize = 0x0;
}
impl<T: Copy + SizedEncode> SizedEncode for Cell<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
@ -178,12 +184,6 @@ impl<T: SizedEncode + ?Sized> SizedEncode for Mutex<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
#[cfg(feature = "never-type")]
#[cfg_attr(doc, doc(cfg(feature = "never-type")))]
impl SizedEncode for ! {
const MAX_ENCODED_SIZE: usize = 0x0;
}
impl<T: SizedEncode> SizedEncode for Option<T> {
const MAX_ENCODED_SIZE: usize =
bool::MAX_ENCODED_SIZE
@ -478,65 +478,65 @@ impl_non_zero!(usize);
impl_atomic! {
width: "8",
ty: bool,
atomic_ty: std::sync::atomic::AtomicBool,
atomic_ty: core::sync::atomic::AtomicBool,
}
impl_atomic! {
width: "16",
ty: i16,
atomic_ty: std::sync::atomic::AtomicI16,
atomic_ty: core::sync::atomic::AtomicI16,
}
impl_atomic! {
width: "32",
ty: i32,
atomic_ty: std::sync::atomic::AtomicI32,
atomic_ty: core::sync::atomic::AtomicI32,
}
impl_atomic! {
width: "64",
ty: i64,
atomic_ty: std::sync::atomic::AtomicI64,
atomic_ty: core::sync::atomic::AtomicI64,
}
impl_atomic! {
width: "8",
ty: i8,
atomic_ty: std::sync::atomic::AtomicI8,
atomic_ty: core::sync::atomic::AtomicI8,
}
impl_atomic! {
width: "ptr",
ty: isize,
atomic_ty: std::sync::atomic::AtomicIsize,
atomic_ty: core::sync::atomic::AtomicIsize,
}
impl_atomic! {
width: "16",
ty: u16,
atomic_ty: std::sync::atomic::AtomicU16,
atomic_ty: core::sync::atomic::AtomicU16,
}
impl_atomic! {
width: "32",
ty: u32,
atomic_ty: std::sync::atomic::AtomicU32,
atomic_ty: core::sync::atomic::AtomicU32,
}
impl_atomic! {
width: "64",
ty: u64,
atomic_ty: std::sync::atomic::AtomicU64,
atomic_ty: core::sync::atomic::AtomicU64,
}
impl_atomic! {
width: "8",
ty: u8,
atomic_ty: std::sync::atomic::AtomicU8,
atomic_ty: core::sync::atomic::AtomicU8,
}
impl_atomic! {
width: "ptr",
ty: usize,
atomic_ty: std::sync::atomic::AtomicUsize,
atomic_ty: core::sync::atomic::AtomicUsize,
}

View file

@ -6,10 +6,9 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use oct::encode::{Encode, SizedEncode};
use std::convert::Infallible;
use std::marker::PhantomData;
use std::net::{
use core::convert::Infallible;
use core::marker::PhantomData;
use core::net::{
IpAddr,
Ipv4Addr,
Ipv6Addr,
@ -17,7 +16,8 @@ use std::net::{
SocketAddrV4,
SocketAddrV6,
};
use std::num::NonZero;
use core::num::NonZero;
use oct::encode::{Encode, SizedEncode};
macro_rules! assert_encoded_size {
($ty:ty, $value:expr$(,)?) => {{

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A character could not be decoded.
///
@ -31,3 +33,12 @@ impl Display for CharDecodeError {
}
impl Error for CharDecodeError { }
impl From<Infallible> for CharDecodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -9,12 +9,13 @@
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A collection could not be decoded.
///
/// This type is intended as a partially-generic decode error for collections.
/// It supports denoting an error for when the collection's length is invalid -- see the [`BadLength`](Self::BadLength) variant -- and when an element is invalid -- see the [`Item`](Self::BadItem)) variant.
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub enum CollectionDecodeError<L, I> {
/// The collection length could not be decoded or was invalid.
@ -63,6 +64,15 @@ where
}
}
impl<L, I> From<Infallible> for CollectionDecodeError<L, I> {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}
impl<L, I> From<CollectionDecodeError<L, I>> for Infallible
where
L: Into<Self>,

View file

@ -9,12 +9,13 @@
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A collection could not be encoded.
///
/// This type is intended as a partially-generic encode error for collections.
/// It supports denoting an error for when the collection's length is invalid -- see the [`BadLength`](Self::BadLength) variant -- and when an element is invalid -- see the [`Item`](Self::BadItem)) variant.
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub enum CollectionEncodeError<L, I> {
/// The collection length could not be encoded.
@ -56,6 +57,15 @@ where
}
}
impl<L, I> From<Infallible> for CollectionEncodeError<L, I> {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}
impl<L, I> From<CollectionEncodeError<L, I>> for Infallible
where
L: Into<Self>,

View file

@ -11,30 +11,29 @@ use crate::decode::Decode;
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Debug, Display, Formatter};
use core::hint::unreachable_unchecked;
/// An enumeration could not be decoded.
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub enum EnumDecodeError<D: Decode, F> {
pub enum EnumDecodeError<T, D, F> {
/// The discriminant could not be decoded.
InvalidDiscriminant(D::Error),
InvalidDiscriminant(D),
/// An otherwise valid discriminant has not been assigned.
///
/// Remember that this error does **not** indicate that the discriminant couldn't be decoded, merely that it does not match with that of any variant.
/// See also [`InvalidDiscriminant`](Self::InvalidDiscriminant).
UnassignedDiscriminant {
/// The unassigned discriminant value.
value: D
},
UnassignedDiscriminant(T),
/// A field could not be encoded.
BadField(F),
}
impl<D, F> Display for EnumDecodeError<D, F>
impl<T, D, F> Display for EnumDecodeError<T, D, F>
where
D: Decode<Error: Display> + Display,
T: Display,
D: Display,
F: Display,
{
#[inline]
@ -43,7 +42,7 @@ where
Self::InvalidDiscriminant(ref e)
=> write!(f, "discriminant could not be decoded: {e}"),
Self::UnassignedDiscriminant { ref value }
Self::UnassignedDiscriminant(ref value)
=> write!(f, "`{value}` is not an assigned discriminant for the given enumeration"),
Self::BadField(ref e)
@ -52,9 +51,10 @@ where
}
}
impl<D, F> Error for EnumDecodeError<D, F>
impl<T, D, F> Error for EnumDecodeError<T, D, F>
where
D: Debug + Decode<Error: Error + 'static> + Display,
T: Debug + Display,
D: Error + 'static,
F: Error + 'static,
{
#[inline]
@ -69,13 +69,22 @@ where
}
}
impl<D, F> From<EnumDecodeError<D, F>> for Infallible
impl<T, D, F> From<Infallible> for EnumDecodeError<T, D, F> {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}
impl<T, D, F> From<EnumDecodeError<T, D, F>> for Infallible
where
D: Decode<Error: Into<Self>>,
T: Decode<Error: Into<Self>>,
F: Into<Self>,
{
#[inline(always)]
fn from(_value: EnumDecodeError<D, F>) -> Self {
fn from(_value: EnumDecodeError<T, D, F>) -> Self {
unreachable!()
}
}

View file

@ -6,18 +6,17 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::encode::Encode;
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Debug, Display, Formatter};
use core::hint::unreachable_unchecked;
/// An enumeration could not be encoded.
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub enum EnumEncodeError<D: Encode, F> {
pub enum EnumEncodeError<D, F> {
/// The discriminant could not be encoded.
BadDiscriminant(D::Error),
BadDiscriminant(D),
/// A field could not be encoded.
BadField(F),
@ -25,7 +24,7 @@ pub enum EnumEncodeError<D: Encode, F> {
impl<D, F> Display for EnumEncodeError<D, F>
where
D: Display + Encode<Error: Display>,
D: Display,
F: Display,
{
#[inline]
@ -42,7 +41,7 @@ where
impl<D, F> Error for EnumEncodeError<D, F>
where
D: Debug + Display + Encode<Error: Error + 'static>,
D: Error + 'static,
F: Error + 'static,
{
#[inline]
@ -55,9 +54,18 @@ where
}
}
impl<D, F> From<Infallible> for EnumEncodeError<D, F> {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}
impl<D, F> From<EnumEncodeError<D, F>> for Infallible
where
D: Encode<Error: Into<Self>>,
D: Into<Self>,
F: Into<Self>,
{
#[inline(always)]

View file

@ -6,16 +6,15 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::PrimitiveDiscriminant;
use crate::decode::Decode;
use crate::{PrimDiscriminant, PrimRepr};
use crate::error::{
CollectionDecodeError,
EnumDecodeError,
ItemDecodeError,
NonZeroDecodeError,
LengthError,
StringError,
SystemTimeDecodeError,
Utf8Error,
};
use core::convert::Infallible;
@ -25,28 +24,25 @@ use core::hint::unreachable_unchecked;
/// A generic decoding error type.
///
/// The intended use of this type is by [derived](derive@Decode) implementations of [`Decode`].
/// The intended use of this type is by [derived](derive@crate::decode::Decode) implementations of [`crate::decode::Decode`].
/// Manual implementors are recommended to use a custom or less generic type for the sake of efficiency.
#[derive(Debug, Eq, PartialEq)]
#[must_use]
#[non_exhaustive]
#[derive(Debug, Eq, PartialEq)]
pub enum GenericDecodeError {
/// A string contained a non-UTF-8 sequence.
BadString(StringError),
BadString(Utf8Error),
/// A non-null integer was null.
/// A non-zero integer was null.
NullInteger(NonZeroDecodeError),
/// A statically-sized buffer was too small.
/// A size-constrained buffer was too small.
SmallBuffer(LengthError),
/// An unassigned discriminant value was encountered.
///
/// The contained value denotes the raw, numerical value of the discriminant.
UnassignedDiscriminant {
/// The raw value of the discriminant.
value: u128
},
UnassignedDiscriminant(PrimDiscriminant),
/// The [`SystemTime`](std::time::SystemTime) type was too narrow.
#[cfg(feature = "std")]
@ -67,7 +63,7 @@ impl Display for GenericDecodeError {
Self::SmallBuffer(ref e)
=> write!(f, "{e}"),
Self::UnassignedDiscriminant { value }
Self::UnassignedDiscriminant(value)
=> write!(f, "discriminant value `{value:#X} has not been assigned"),
#[cfg(feature = "std")]
@ -112,19 +108,20 @@ where
}
}
impl<D, F> From<EnumDecodeError<D, F>> for GenericDecodeError
impl<T, D, F> From<EnumDecodeError<T, D, F>> for GenericDecodeError
where
D: Decode<Error: Into<Self>> + PrimitiveDiscriminant,
T: PrimRepr,
D: Into<Self>,
F: Into<Self>,
{
#[inline(always)]
fn from(value: EnumDecodeError<D, F>) -> Self {
fn from(value: EnumDecodeError<T, D, F>) -> Self {
use EnumDecodeError as Error;
match value {
Error::InvalidDiscriminant(e) => e.into(),
Error::UnassignedDiscriminant { value } => Self::UnassignedDiscriminant { value: value.to_u128() },
Error::UnassignedDiscriminant(value) => Self::UnassignedDiscriminant(value.into_prim_discriminant()),
Error::BadField(e) => e.into(),
}
@ -161,13 +158,6 @@ impl From<LengthError> for GenericDecodeError {
}
}
impl From<StringError> for GenericDecodeError {
#[inline(always)]
fn from(value: StringError) -> Self {
Self::BadString(value)
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl From<SystemTimeDecodeError> for GenericDecodeError {
@ -176,3 +166,10 @@ impl From<SystemTimeDecodeError> for GenericDecodeError {
Self::NarrowSystemTime(value)
}
}
impl From<Utf8Error> for GenericDecodeError {
#[inline(always)]
fn from(value: Utf8Error) -> Self {
Self::BadString(value)
}
}

View file

@ -6,7 +6,6 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::encode::Encode;
use crate::error::{
CollectionEncodeError,
EnumEncodeError,
@ -23,11 +22,11 @@ use core::hint::unreachable_unchecked;
/// A generic encoding error type.
///
/// The intended use of this type is by [derived](derive@crate::encode::Encode) implementations of [`Encode`].
/// The intended use of this type is by [derived](derive@crate::encode::Encode) implementations of [`crate::encode::Encode`].
/// Manual implementors are recommended to use a custom or less generic type for the sake of efficiency.
#[derive(Debug)]
#[must_use]
#[non_exhaustive]
#[derive(Debug)]
pub enum GenericEncodeError {
/// A [`RefCell`](core::cell::RefCell) object could not be borrowed.
BadBorrow(BorrowError),
@ -96,7 +95,7 @@ where
impl<D, F> From<EnumEncodeError<D, F>> for GenericEncodeError
where
D: Encode<Error: Into<Self>>,
D: Into<Self>,
F: Into<Self>,
{
#[inline(always)]

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// An input-related error.
///
@ -39,3 +41,12 @@ impl Display for InputError {
}
impl Error for InputError { }
impl From<Infallible> for InputError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// An [`isize`] value could not be decoded.
///
@ -33,3 +35,12 @@ impl Display for IsizeEncodeError {
}
impl Error for IsizeEncodeError { }
impl From<Infallible> for IsizeEncodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -9,11 +9,12 @@
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Debug, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A collection's item could not be decoded.
///
/// See also [`CollectionDecodeError`](crate::error::CollectionDecodeError).
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub struct ItemDecodeError<I, E> {
/// The index of the invalid item.
@ -45,6 +46,15 @@ where
}
}
impl<I, E> From<Infallible> for ItemDecodeError<I, E> {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}
impl<I, E> From<ItemDecodeError<I, E>> for Infallible {
#[inline(always)]
fn from(_value: ItemDecodeError<I, E>) -> Self {

View file

@ -9,11 +9,12 @@
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Debug, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A collection's item could not be encoded.
///
/// See also [`CollectionEncodeError`](crate::error::CollectionEncodeError).
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub struct ItemEncodeError<I, E> {
/// The index of the invalid item.
@ -45,6 +46,15 @@ where
}
}
impl<I, E> From<Infallible> for ItemEncodeError<I, E> {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}
impl<I, E: Into<Self>> From<ItemEncodeError<I, E>> for Infallible {
#[inline(always)]
fn from(_value: ItemEncodeError<I, E>) -> Self {

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A collection buffer was too small to contain all of its elements.
///
@ -35,3 +37,12 @@ impl Display for LengthError {
}
impl Error for LengthError { }
impl From<Infallible> for LengthError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -28,7 +28,6 @@ use_mod!(pub length_error);
use_mod!(pub non_zero_decode_error);
use_mod!(pub output_error);
use_mod!(pub ref_cell_encode_error);
use_mod!(pub string_error);
use_mod!(pub usize_encode_error);
use_mod!(pub utf8_error);

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A non-zero integer could not be decoded.
///
@ -23,3 +25,12 @@ impl Display for NonZeroDecodeError {
}
impl Error for NonZeroDecodeError { }
impl From<Infallible> for NonZeroDecodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
#[derive(Debug, Eq, PartialEq)]
#[must_use]
@ -39,3 +41,12 @@ impl Display for OutputError {
}
impl Error for OutputError { }
impl From<Infallible> for OutputError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -7,8 +7,10 @@
// <https://mozilla.org/MPL/2.0/>.
use core::cell::BorrowError;
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A reference cell could not be encoded.
///
@ -48,3 +50,12 @@ impl<E: Error + 'static> Error for RefCellEncodeError<E> {
}
}
}
impl<E> From<Infallible> for RefCellEncodeError<E> {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -1,48 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::error::{LengthError, Utf8Error};
use core::error::Error;
use core::fmt::{self, Display, Formatter};
/// String error variants.
#[derive(Debug, Eq, PartialEq)]
#[non_exhaustive]
#[must_use]
pub enum StringError {
/// An invalid UTF-8 sequence was encountered.
BadUtf8(Utf8Error),
/// A fixed-size buffer was too small.
SmallBuffer(LengthError),
}
impl Display for StringError {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
Self::BadUtf8(ref e)
=> write!(f, "bad utf-8: {e}"),
Self::SmallBuffer(ref e)
=> write!(f, "buffer too small: {e}"),
}
}
}
impl Error for StringError {
#[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
Self::BadUtf8(ref e) => Some(e),
Self::SmallBuffer(ref e) => Some(e),
}
}
}

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// The [`SystemTime`](std::time::SystemTime) type could not represent a UNIX timestamp.
///
@ -31,3 +33,13 @@ impl Display for SystemTimeDecodeError {
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Error for SystemTimeDecodeError { }
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl From<Infallible> for SystemTimeDecodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// A [`usize`] value could not be decoded.
///
@ -32,3 +34,12 @@ impl Display for UsizeEncodeError {
}
impl Error for UsizeEncodeError { }
impl From<Infallible> for UsizeEncodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -6,8 +6,10 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::hint::unreachable_unchecked;
/// An invalid UTF-8 sequence was encountered.
#[derive(Debug, Eq, PartialEq)]
@ -28,3 +30,12 @@ impl Display for Utf8Error {
}
impl Error for Utf8Error { }
impl From<Infallible> for Utf8Error {
#[inline(always)]
fn from(_value: Infallible) -> Self {
// SAFETY: `Infallible` objects can never be con-
// structed.
unsafe { unreachable_unchecked() };
}
}

View file

@ -28,18 +28,19 @@
//!
//! | Benchmark | [Bincode] | [Borsh] | Oct | [Postcard] |
//! | :--------------------------------- | --------: | ------: | -----: | ---------: |
//! | `encode_u8` | 0.977 | 0.871 | 0.754 | 0.916 |
//! | `encode_u32` | 0.967 | 0.983 | 0.730 | 2.727 |
//! | `encode_u128` | 2.178 | 2.175 | 1.481 | 6.002 |
//! | `encode_u8` | 0.927 | 0.939 | 0.742 | 0.896 |
//! | `encode_u32` | 1.069 | 1.007 | 0.738 | 2.732 |
//! | `encode_u128` | 2.180 | 2.204 | 1.522 | 6.412 |
//! | `encode_char` | 2.474 | 1.261 | 0.817 | 2.480 |
//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.000 |
//! | `encode_struct_unnamed` | 1.206 | 1.168 | 0.805 | 2.356 |
//! | `encode_struct_named` | 3.021 | 1.532 | 0.952 | 3.013 |
//! | `encode_enum_unit` | 0.245 | 0.294 | 0.000 | 0.294 |
//! | `decode_u8` | 0.952 | 0.895 | 0.885 | 0.894 |
//! | `decode_non_zero_u8` | 1.215 | 1.250 | 1.229 | 1.232 |
//! | `decode_bool` | 1.204 | 1.224 | 1.126 | 1.176 |
//! | **Total time** &#8594; | 11.964 | 10.392 | 7.963 | 18.609 |
//! | **Total deviation (p.c.)** &#8594; | +50 | +31 | ±0 | +134 |
//! | `encode_struct_unnamed` | 1.245 | 1.146 | 0.834 | 2.378 |
//! | `encode_struct_named` | 3.037 | 1.541 | 0.961 | 3.014 |
//! | `encode_enum_unit` | 0.250 | 0.297 | 0.000 | 0.296 |
//! | `decode_u8` | 0.992 | 0.926 | 0.915 | 0.981 |
//! | `decode_non_zero_u8` | 1.218 | 1.215 | 1.225 | 1.238 |
//! | `decode_bool` | 1.064 | 1.088 | 1.046 | 1.080 |
//! | **Total time** &#8594; | 14.456 | 11.624 | 8.800 | 21.509 |
//! | **Total deviation (p.c.)** &#8594; | +64 | +32 | ±0 | +144 |
//!
//! [Bincode]: https://crates.io/crates/bincode/
//! [Borsh]: https://crates.io/crates/borsh/
@ -53,11 +54,11 @@
//!
//! # Data model
//!
//! Most primitives encode losslessly, with the main exceptions being [`usize`] and [`isize`].
//! These are instead first cast as [`u16`] and [`i16`], respectively, due to portability concerns (with respect to embedded systems).
//! Primitives encode losslessly by default, although [`usize`] and [`isize`] are the exception to this.
//! Due to their machine-dependent representation, these are truncated to the smallest subset of values guaranteed by Rust, with this equating to a cast to [`u16`] or [`i16`], respectively.
//!
//! Numerical primitives in general encode as little endian (and **not** ["network order"](https://en.wikipedia.org/wiki/Endianness#Networking)).
//! It is recommended for implementors to follow this convention as well.
//! Numerical types in general (including `char`) are encoded as little endian (and **not** ["network order"](https://en.wikipedia.org/wiki/Endianness#Networking) as is the norm in TCP/UDP/IP).
//! It is recommended for implementors of custom types to adhere to this convention as well.
//!
//! See specific types' implementations for notes on their data models.
//!
@ -68,54 +69,10 @@
//!
//! This crate revolves around the [`Encode`](encode::Encode) and [`Decode`](decode::Decode) traits, both of which handle conversions to and from byte streams.
//!
//! Many standard types come implemented with Oct, including most primitives as well as some standard library types such as [`Option`] and [`Result`].
//! Some [features](#feature-flags) enable an extended set of implementations.
//! These traits are already implemented by Oct for a large set of the standard types, such as [`Option`] and [`Mutex`](std::sync::Mutex).
//! Some [features](#feature-flags) enable an extended set of implementations that are locked behind unstable feature gates or other crates.
//!
//! It is recommended in most cases to simply derive these two traits for user-defined types, although this is only supported for enumerations and structures -- not untagged unions.
//! When deriving, each field is *chained* according to declaration order:
//!
//! ```rust
//! use oct::decode::Decode;
//! use oct::encode::Encode;
//! use oct::slot::Slot;
//!
//! #[derive(Debug, Decode, Encode, PartialEq)]
//! struct Ints {
//! value0: u8,
//! value1: u16,
//! value2: u32,
//! value3: u64,
//! value4: u128,
//! }
//!
//! const VALUE: Ints = Ints {
//! value0: 0x00,
//! value1: 0x02_01,
//! value2: 0x06_05_04_03,
//! value3: 0x0E_0D_0C_0B_0A_09_08_07,
//! value4: 0x1E_1D_1C_1B_1A_19_18_17_16_15_14_13_12_11_10_0F,
//! };
//!
//! let mut buf = Slot::with_capacity(0x100);
//!
//! buf.write(VALUE).unwrap();
//!
//! assert_eq!(buf.len(), 0x1F);
//!
//! assert_eq!(
//! buf,
//! [
//! 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
//! 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
//! 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
//! 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E
//! ].as_slice(),
//! );
//!
//! assert_eq!(buf.read().unwrap(), VALUE);
//! ```
//!
//! The following is a more complete example of a UDP server/client for geographic data:
//! The following is an example of a UDP server/client for geographic data:
//!
//! ```rust
//! use oct::decode::Decode;
@ -126,8 +83,8 @@
//! use std::thread::spawn;
//!
//! // City, region, etc.:
//! #[derive(Clone, Copy, Debug, Decode, Encode, Eq, PartialEq, SizedEncode)]
//! #[non_exhaustive]
//! #[derive(Clone, Copy, Debug, Decode, Encode, Eq, PartialEq, SizedEncode)]
//! enum Area {
//! AlQuds,
//! Byzantion,
@ -137,8 +94,8 @@
//! }
//!
//! // Client-to-server message:
//! #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
//! #[non_exhaustive]
//! #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
//! enum Request {
//! AtmosphericHumidity { area: Area },
//! AtmosphericPressure { area: Area },
@ -147,8 +104,8 @@
//! }
//!
//! // Server-to-client message:
//! #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
//! #[non_exhaustive]
//! #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)]
//! enum Response {
//! AtmosphericHumidity(f64),
//! AtmosphericPressure(f64), // Pascal
@ -159,8 +116,8 @@
//! struct Party {
//! pub socket: UdpSocket,
//!
//! pub request_buf: Slot::<Request>,
//! pub response_buf: Slot::<Response>,
//! pub request_buf: Slot<Request>,
//! pub response_buf: Slot<Response>,
//! }
//!
//! impl Party {
@ -230,13 +187,18 @@
//! * `proc-macro`: Pulls procedural macros from the [`oct-macros`](https://crates.io/crates/oct-macros/) crate
//! * `std`: Enables implementations for types [`std`], e.g. [`Mutex`](std::sync::Mutex) and [`RwLock`](std::sync::RwLock)
//!
//! The following features can additionally be enabled for support with nightly-only constructs:
//!
//! * `f128`: Enable implementations for the [`f128`] type
//! * `f16`: Enable implementations for the [`f16`] type
//!
//! # Documentation
//!
//! Oct has its documentation written alongside its source code for use by `rustdoc`.
//! See [Docs.rs](https://docs.rs/oct/latest/oct/) for an on-line, rendered instance.
//!
//! Currently, these docs make use of some unstable features for the sake of readability.
//! The nightly toolchain is therefore required when rendering them.
//! The nightly toolchain is therefore always required when rendering them or or running tests herein.
//!
//! # Contribution
//!
@ -256,9 +218,8 @@
#![no_std]
#![cfg_attr(feature = "f16", feature(f16))]
#![cfg_attr(feature = "f128", feature(f128))]
#![cfg_attr(feature = "never-type", feature(never_type))]
#![cfg_attr(feature = "f128", feature(f128))]
#![cfg_attr(feature = "f16", feature(f16))]
#![cfg_attr(doc, feature(doc_cfg, rustdoc_internals))]
@ -282,8 +243,6 @@ macro_rules! use_mod {
}
pub(crate) use use_mod;
use_mod!(pub primitive_discriminant);
pub mod decode;
pub mod encode;
pub mod error;
@ -292,3 +251,6 @@ pub mod vec;
#[cfg(feature = "alloc")]
pub mod slot;
use_mod!(pub prim_discriminant);
use_mod!(pub prim_repr);

View file

@ -0,0 +1,84 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::PrimRepr;
#[cfg(test)]
mod test;
/// A generic-but-primitive discriminant.
///
/// This type represents all types supported by the `repr` attribute on enumerations.
#[expect(missing_docs)]
#[non_exhaustive]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum PrimDiscriminant {
U8(u8),
U16(u16),
U32(u32),
U64(u64),
U128(u128),
Usize(usize),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
I128(i128),
Isize(isize),
}
impl<T: PrimRepr> From<T> for PrimDiscriminant {
#[inline(always)]
fn from(value: T) -> Self {
value.into_prim_discriminant()
}
}
macro_rules! impl_fmt {
($fmt:path) => {
impl $fmt for ::oct::PrimDiscriminant {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
macro_rules! match_ty {
($variant:ident => $ty:ty) => {
if let Self::$variant(ref v) = *self {
<$ty as $fmt>::fmt(v, f)?;
::core::write!(f, ::core::stringify!($ty))?;
}
};
}
match_ty!(U8 => u8);
match_ty!(U16 => u16);
match_ty!(U32 => u32);
match_ty!(U64 => u64);
match_ty!(U128 => u128);
match_ty!(Usize => usize);
match_ty!(I8 => i8);
match_ty!(I16 => i16);
match_ty!(I32 => i32);
match_ty!(I64 => i64);
match_ty!(I128 => i128);
match_ty!(Isize => isize);
::core::result::Result::Ok(())
}
}
};
}
impl_fmt!(core::fmt::Binary);
impl_fmt!(core::fmt::Debug);
impl_fmt!(core::fmt::Display);
impl_fmt!(core::fmt::LowerExp);
impl_fmt!(core::fmt::LowerHex);
impl_fmt!(core::fmt::Octal);
impl_fmt!(core::fmt::UpperExp);
impl_fmt!(core::fmt::UpperHex);

View file

@ -0,0 +1,39 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use alloc::format;
use oct::PrimDiscriminant;
#[test]
fn test_primitive_discriminant_fmt() {
let value = PrimDiscriminant::I128(0x45);
assert_eq!(format!("{value}"), "69i128");
assert_eq!(format!("{value:+}"), "+69i128");
assert_eq!(format!("{value:b}"), "1000101i128");
assert_eq!(format!("{value:#b}"), "0b1000101i128");
assert_eq!(format!("{value:o}"), "105i128");
assert_eq!(format!("{value:#o}"), "0o105i128");
assert_eq!(format!("{value:x}"), "45i128");
assert_eq!(format!("{value:#x}"), "0x45i128");
assert_eq!(format!("{value:X}"), "45i128");
assert_eq!(format!("{value:#X}"), "0x45i128");
let value = PrimDiscriminant::I8(-0x45);
assert_eq!(format!("{value}"), "-69i8");
assert_eq!(format!("{value:+}"), "-69i8");
assert_eq!(format!("{value:b}"), "10111011i8");
assert_eq!(format!("{value:#b}"), "0b10111011i8");
assert_eq!(format!("{value:o}"), "273i8");
assert_eq!(format!("{value:#o}"), "0o273i8");
assert_eq!(format!("{value:x}"), "bbi8");
assert_eq!(format!("{value:#x}"), "0xbbi8");
assert_eq!(format!("{value:X}"), "BBi8");
assert_eq!(format!("{value:#X}"), "0xBBi8");
}

52
oct/src/prim_repr/mod.rs Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::PrimDiscriminant;
mod sealed {
/// Denotes a primitive enumeration representation.
///
/// See the public [`PrimRepr`](crate::PrimRepr) trait for more information.
pub trait PrimRepr { }
}
pub(crate) use sealed::PrimRepr as SealedPrimRepr;
/// Denotes a primitive enumeration representation.
pub trait PrimRepr: Copy + SealedPrimRepr + Sized {
/// Converts `self` into a [`PrimDiscriminant`] object.
#[must_use]
fn into_prim_discriminant(self) -> PrimDiscriminant;
}
macro_rules! impl_prim_repr {
($ty:ty => $variant:ident) => {
impl ::oct::SealedPrimRepr for $ty { }
impl PrimRepr for $ty {
#[inline(always)]
fn into_prim_discriminant(self) -> ::oct::PrimDiscriminant {
::oct::PrimDiscriminant::$variant(self)
}
}
};
}
impl_prim_repr!(u8 => U8);
impl_prim_repr!(u16 => U16);
impl_prim_repr!(u32 => U32);
impl_prim_repr!(u64 => U64);
impl_prim_repr!(u128 => U128);
impl_prim_repr!(usize => Usize);
impl_prim_repr!(i8 => I8);
impl_prim_repr!(i16 => I16);
impl_prim_repr!(i32 => I32);
impl_prim_repr!(i64 => I64);
impl_prim_repr!(i128 => I128);
impl_prim_repr!(isize => Isize);

View file

@ -1,56 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
mod sealed {
/// Denotes a primitive, integral discriminant type.
///
/// See the public [`PrimitiveDiscriminant`](crate::PrimitiveDiscriminant) trait for more information.
pub trait PrimitiveDiscriminant {
/// Interprets the discriminant value as `u128`.
///
/// The returned value has exactly the same representation as the original value except that it is zero-extended to fit.
#[must_use]
fn to_u128(self) -> u128;
}
}
pub(crate) use sealed::PrimitiveDiscriminant as SealedPrimitiveDiscriminant;
/// Denotes a primitive, integral discriminant type.
///
/// This trait is specifically defined as a type which may be used as a representation in the `repr` attribute, i.e. [`u8`], [`i8`], [`u16`], [`i16`], [`u32`], [`i32`], [`u64`], [`i64`], [`usize`], and [`isize`].
///
/// On nightly, this additionally includes [`u128`] and [`i128`] (see the tracking issue for [`repr128`](https://github.com/rust-lang/rust/issues/56071/)), although this trait is implemented for these two types anyhow.
pub trait PrimitiveDiscriminant: Copy + SealedPrimitiveDiscriminant + Sized { }
macro_rules! impl_primitive_discriminant {
($ty:ty) => {
impl ::oct::SealedPrimitiveDiscriminant for $ty {
#[allow(clippy::cast_lossless)]
#[inline(always)]
fn to_u128(self) -> u128 {
self as u128
}
}
impl ::oct::PrimitiveDiscriminant for $ty { }
};
}
impl_primitive_discriminant!(u8);
impl_primitive_discriminant!(i8);
impl_primitive_discriminant!(u16);
impl_primitive_discriminant!(i16);
impl_primitive_discriminant!(u32);
impl_primitive_discriminant!(i32);
impl_primitive_discriminant!(u64);
impl_primitive_discriminant!(i64);
impl_primitive_discriminant!(u128);
impl_primitive_discriminant!(i128);
impl_primitive_discriminant!(usize);
impl_primitive_discriminant!(isize);

View file

@ -7,7 +7,7 @@
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod tests;
mod test;
use crate::decode::{Decode, Input};
use crate::encode::{Encode, Output, SizedEncode};
@ -93,7 +93,7 @@ impl<T> Slot<T> {
let buf = {
let buf = ptr::slice_from_raw_parts_mut(ptr, cap);
Box::from_raw(buf)
unsafe { Box::from_raw(buf) }
};
Self {

View file

@ -1,64 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::string::String;
use core::cmp::Ordering;
impl<const N: usize> Ord for String<N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<const N: usize, const M: usize> PartialEq<String<M>> for String<N> {
#[inline(always)]
fn eq(&self, other: &String<M>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> PartialEq<&str> for String<N> {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialEq<alloc::string::String> for String<N> {
#[inline(always)]
fn eq(&self, other: &alloc::string::String) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize, const M: usize> PartialOrd<String<M>> for String<N> {
#[inline(always)]
fn partial_cmp(&self, other: &String<M>) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl<const N: usize> PartialOrd<&str> for String<N> {
#[inline(always)]
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
self.as_str().partial_cmp(*other)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialOrd<alloc::string::String> for String<N> {
#[inline(always)]
fn partial_cmp(&self, other: &alloc::string::String) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}

View file

@ -1,47 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::string::String;
use crate::decode::{Decode, DecodeBorrowed, Input};
use crate::encode::{Encode, Output, SizedEncode};
use crate::error::{CollectionDecodeError, LengthError, StringError, Utf8Error};
impl<const N: usize> Decode for String<N> {
type Error = CollectionDecodeError<LengthError, Utf8Error>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let len = Decode::decode(input).unwrap();
let data = input.read(len).unwrap();
Self::from_utf8(data)
.map_err(|e| match e {
StringError::BadUtf8(e) => CollectionDecodeError::BadItem(e),
StringError::SmallBuffer(e) => CollectionDecodeError::BadLength(e),
})
}
}
impl<const N: usize> DecodeBorrowed<str> for String<N> { }
impl<const N: usize> Encode for String<N> {
type Error = <str as Encode>::Error;
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.as_str().encode(output)
}
}
impl<const N: usize> SizedEncode for String<N> {
const MAX_ENCODED_SIZE: usize =
usize::MAX_ENCODED_SIZE
+ u8::MAX_ENCODED_SIZE * N;
}

View file

@ -1,359 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::error::{LengthError, StringError, Utf8Error};
use crate::vec::Vec;
use crate::string::String;
use core::borrow::{Borrow, BorrowMut};
use core::mem::{ManuallyDrop, MaybeUninit};
use core::ops::{Deref, DerefMut};
use core::ptr::copy_nonoverlapping;
use core::slice;
use core::str::{self, FromStr};
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "std")]
use {
std::ffi::OsStr,
std::net::ToSocketAddrs,
std::path::Path,
};
impl<const N: usize> String<N> {
/// Constructs a fixed-size string from raw parts.
///
/// The provided parts are not tested in any way.
///
/// # Safety
///
/// The value of `len` may not exceed that of `N`.
/// Additionally, the octets in `buf` (from index zero up to the value of `len`) must be valid UTF-8 codepoints.
///
/// If any of these requirements are violated, behaviour is undefined.
#[inline(always)]
#[must_use]
pub const unsafe fn from_raw_parts(buf: [u8; N], len: usize) -> Self {
debug_assert!(len <= N, "cannot construct string that is longer than its capacity");
let buf = unsafe { buf.as_ptr().cast::<[MaybeUninit<u8>; N]>().read() };
Self(Vec::from_raw_parts(buf, len))
}
/// Constructs a new string from UTF-8 octets.
///
/// The passed slice is checked for its validity.
/// For a similar function *without* these checks, see [`from_utf8_unchecked`](Self::from_utf8_unchecked).
///
/// # Errors
///
/// Each byte value must be a valid UTF-8 code point.
#[inline]
pub const fn from_utf8(s: &[u8]) -> Result<Self, StringError> {
if s.len() > N {
return Err(StringError::SmallBuffer(LengthError {
remaining: N,
count: s.len(),
}));
}
let s = match str::from_utf8(s) {
Ok(s) => s,
Err(e) => {
let i = e.valid_up_to();
let c = s[i];
return Err(StringError::BadUtf8(Utf8Error { value: c, index: i }));
}
};
// SAFETY: `s` has been tested to only contain
// valid octets.
let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) };
Ok(this)
}
/// Unsafely constructs a new string from UTF-8 octets.
///
/// # Safety
///
/// Each byte value must be a valid UTF-8 code point.
/// The behaviour of a programme that passes invalid values to this function is undefined.
#[inline]
#[must_use]
pub const unsafe fn from_utf8_unchecked(s: &[u8]) -> Self {
debug_assert!(s.len() <= N, "cannot construct string from utf-8 sequence that is longer");
let mut buf = [0x00; N];
copy_nonoverlapping(s.as_ptr(), buf.as_mut_ptr(), s.len());
// SAFETY: `s` is guaranteed by the caller to only
// contain valid octets. It has also been tested to
// not exceed bounds.
Self::from_raw_parts(buf, s.len())
}
/// Gets a pointer to the first octet.
#[inline(always)]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
// This function can only be marked as `const` when
// `const_mut_refs` is implemented. See tracking
// issue #57349 for more information.
/// Gets a mutable pointer to the first octet.
///
#[inline(always)]
#[must_use]
pub const fn as_mut_ptr(&mut self) -> *mut u8 {
self.0.as_mut_ptr()
}
/// Borrows the string as a byte slice.
///
/// The range of the returned slice only includes characters that are "used."
#[inline(always)]
#[must_use]
pub const fn as_bytes(&self) -> &[u8] {
// We need to use `from_raw_parts` to mark this
// function `const`.
let ptr = self.as_ptr();
let len = self.len();
unsafe { slice::from_raw_parts(ptr, len) }
}
/// Borrows the string as a string slice.
///
/// The range of the returned slice only includes characters that are "used."
#[inline(always)]
#[must_use]
pub const fn as_str(&self) -> &str {
// SAFETY: We guarantee that all octets are valid
// UTF-8 code points.
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
/// Mutably borrows the string as a string slice.
///
/// The range of the returned slice only includes characters that are "used."
#[inline(always)]
#[must_use]
pub fn as_mut_str(&mut self) -> &mut str {
// TODO: Mark with `const`.
unsafe {
let ptr = self.as_mut_ptr();
let len = self.len();
let data = slice::from_raw_parts_mut(ptr, len);
core::str::from_utf8_unchecked_mut(data)
}
}
/// Destructs the provided string into its raw parts.
///
/// The returned parts are valid to pass back to [`from_raw_parts`](Self::from_raw_parts).
///
/// The returned byte array is guaranteed to be fully initialised.
/// However, only octets up to an index of [`len`](Self::len) are also guaranteed to be valid UTF-8 codepoints.
#[inline(always)]
#[must_use]
pub const fn into_raw_parts(self) -> ([u8; N], usize) {
let (buf, len) = self.into_bytes().into_raw_parts();
let init_buf = ManuallyDrop::new(buf);
let buf = unsafe { (&raw const init_buf).cast::<[u8; N]>().read() };
(buf, len)
}
/// Converts the string into a vector of bytes.
///
/// The underlying memory of the string is completely reused.
#[inline(always)]
#[must_use]
pub const fn into_bytes(self) -> Vec<u8, N> {
let this = ManuallyDrop::new(self);
// SAFETY: `ManuallyDrop<T>` is transparent to `T`.
// We also aren't dropping `this`, so we can safely
// move out of it.
unsafe { (&raw const this).cast::<Vec<u8, N>>().read() }
}
/// Converts the fixed-size string into a boxed string slice.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[inline(always)]
#[must_use]
pub fn into_boxed_str(self) -> Box<str> {
let Self(v) = self;
unsafe { alloc::str::from_boxed_utf8_unchecked(v.into_boxed_slice()) }
}
/// Converts the fixed-size string into a dynamic string.
///
/// The capacity of the resulting [`alloc::string::String`] object is equal to the value of `N`.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[inline(always)]
#[must_use]
pub fn into_string(self) -> alloc::string::String {
self.into_boxed_str().into_string()
}
}
impl<const N: usize> AsMut<str> for String<N> {
#[inline(always)]
fn as_mut(&mut self) -> &mut str {
self.as_mut_str()
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<const N: usize> AsRef<OsStr> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &OsStr {
self.as_str().as_ref()
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<const N: usize> AsRef<Path> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &Path {
self.as_str().as_ref()
}
}
impl<const N: usize> AsRef<str> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> AsRef<[u8]> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const N: usize> Borrow<str> for String<N> {
#[inline(always)]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> BorrowMut<str> for String<N> {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut str {
self.as_mut_str()
}
}
impl<const N: usize> Deref for String<N> {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<const N: usize> DerefMut for String<N> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_str()
}
}
impl<const N: usize> FromStr for String<N> {
type Err = LengthError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<const N: usize> ToSocketAddrs for String<N> {
type Iter = <str as ToSocketAddrs>::Iter;
#[inline(always)]
fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
self.as_str().to_socket_addrs()
}
}
impl<const N: usize> TryFrom<char> for String<N> {
type Error = <Self as FromStr>::Err;
#[inline(always)]
fn try_from(value: char) -> Result<Self, Self::Error> {
let mut buf = [0x00; 0x4];
let s = value.encode_utf8(&mut buf);
s.parse()
}
}
impl<const N: usize> TryFrom<&str> for String<N> {
type Error = <Self as FromStr>::Err;
#[inline(always)]
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> TryFrom<alloc::string::String> for String<N> {
type Error = <Self as FromStr>::Err;
#[inline(always)]
fn try_from(value: alloc::string::String) -> Result<Self, Self::Error> {
Self::new(&value)
}
}
/// See [`into_boxed_str`](String::into_boxed_str).
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> From<String<N>> for Box<str> {
#[inline(always)]
fn from(value: String<N>) -> Self {
value.into_boxed_str()
}
}
/// See [`into_string`](String::into_string).
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> From<String<N>> for alloc::string::String {
#[inline(always)]
fn from(value: String<N>) -> Self {
value.into_string()
}
}

View file

@ -7,33 +7,41 @@
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod tests;
mod test;
use crate::error::LengthError;
use crate::decode::{self, Decode, DecodeBorrowed};
use crate::encode::{self, Encode, SizedEncode};
use crate::error::{CollectionDecodeError, LengthError, Utf8Error};
use crate::vec::Vec;
use core::borrow::{Borrow, BorrowMut};
use core::cmp::Ordering;
use core::fmt::{self, Debug, Display, Formatter};
use core::hash::{Hash, Hasher};
use core::ops::{Index, IndexMut};
use core::slice::SliceIndex;
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut, Index, IndexMut};
use core::ptr::{copy_nonoverlapping, write_bytes};
use core::slice::{self, SliceIndex};
use core::str::{self, FromStr};
// Comparison facilities:
mod cmp;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
// Encode/decode facilities:
mod code;
// Conversion facilities:
mod conv;
#[cfg(feature = "std")]
use {
std::ffi::OsStr,
std::net::ToSocketAddrs,
std::path::Path,
};
/// String container with maximum length.
///
/// This is in contrast to [`str`] and the standard library's [`String`](alloc::string::String) type -- both of which have no size limit in practice.
/// This is in contrast to [`str`](prim@str) and the standard library's [`String`](alloc::string::String) type -- both of which have no size limit in practice.
///
/// The string itself is encoded in UTF-8 for interoperability wtih Rust's standard string facilities, and partly due to memory concerns.
/// Keep in mind that the size limit specified by `N` then denotes *octets* (or "bytes") and **not** *characters* -- i.e. a value of `8` may translate to anywhere between two and eight characters due to variable-length encoding.
///
/// See [`Vec`] for an equivalent alternative to [`alloc::vec::Vec`].
/// See [`Vec`] for an equivalent alternative to the standard library's [`Vec`](alloc::vec::Vec).
///
/// # Examples
///
@ -44,66 +52,381 @@ mod conv;
/// use oct::string::String;
/// use std::str::FromStr;
///
/// let str0 = String::<0x40>::default(); // Empty string.
/// let str1 = String::<0x40>::from_str("Hello there!").unwrap();
/// let str2 = String::<0x40>::from_str("أنا من أوروپا").unwrap();
/// let str3 = String::<0x40>::from_str("COGITO ERGO SUM").unwrap();
/// let s0 = String::<0x40>::default(); // Empty string.
/// let s1 = String::<0x40>::from_str("Hello there!").unwrap();
/// let s2 = String::<0x40>::from_str("أنا من أوروپا").unwrap();
/// let s3 = String::<0x40>::from_str("COGITO ERGO SUM").unwrap();
///
/// assert_eq!(size_of_val(&str0), size_of_val(&str1));
/// assert_eq!(size_of_val(&str0), size_of_val(&str2));
/// assert_eq!(size_of_val(&str0), size_of_val(&str3));
/// assert_eq!(size_of_val(&str1), size_of_val(&str2));
/// assert_eq!(size_of_val(&str1), size_of_val(&str3));
/// assert_eq!(size_of_val(&str2), size_of_val(&str3));
/// assert_eq!(size_of_val(&s0), size_of_val(&s1));
/// assert_eq!(size_of_val(&s0), size_of_val(&s2));
/// assert_eq!(size_of_val(&s0), size_of_val(&s3));
/// assert_eq!(size_of_val(&s1), size_of_val(&s2));
/// assert_eq!(size_of_val(&s1), size_of_val(&s3));
/// assert_eq!(size_of_val(&s2), size_of_val(&s3));
/// ```
#[derive(Clone, Default, Eq)]
#[repr(transparent)]
pub struct String<const N: usize>(Vec<u8, N>);
#[derive(Clone, Copy)]
pub struct String<const N: usize> {
len: usize,
buf: [u8; N],
}
impl<const N: usize> String<N> {
/// Constructs a new, fixed-size string.
/// Constructs a new, size-constrained string.
///
/// Note that string is not required to completely fill out its size-constraint.
/// The provided string `s` is checked to be containable within `N` bytes.
/// See also [`new_unchecked`](Self::new_unchecked).
///
/// # Errors
///
/// If the internal buffer cannot contain the entirety of `s`, then an error is returned.
#[inline]
pub const fn new(s: &str) -> Result<Self, LengthError> {
if s.len() > N {
let len = s.len();
if len > N {
return Err(LengthError {
remaining: N,
count: s.len(),
count: len,
});
}
let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) };
let mut buf = [0x00; N];
unsafe {
let src = s.as_ptr();
let dst = buf.as_mut_ptr().cast();
copy_nonoverlapping(src, dst, len);
}
// SAFETY: `str` can be assumed to only contain
// valid UTF-8 data.
let this = unsafe { Self::from_raw_parts(buf, len) };
Ok(this)
}
/// Returns the length of the string.
/// Unsafely constructs a new, size-constrained string.
///
/// This does not necessarily equate to the value of `N`, as the internal buffer may be used but partially.
/// See also [`new`](Self::new) for a safe alternative to this constructor.
///
/// Also remember that the returned value only denotes the octet count and not characters, graphemes, etc.
/// # Safety
///
/// If the internal buffer cannot contain the entirety of `s`, then the call to this constructor will result in undefined behaviour.
#[inline]
pub const unsafe fn new_unchecked(s: &str) -> Self {
let len = s.len();
let mut buf = [0x00; N];
debug_assert!(len <= N, "cannot construct string from string slice that is longer");
unsafe {
let src = s.as_ptr();
let dst = buf.as_mut_ptr().cast();
copy_nonoverlapping(src, dst, len);
}
// SAFETY: `str` is guaranteed by the caller to be
// valid.
unsafe { Self::from_raw_parts(buf, len) }
}
/// Constructs a new string from UTF-8 octets.
///
/// The passed slice is checked for its validity.
/// For a similar function *without* these checks, see [`from_utf8_unchecked`](Self::from_utf8_unchecked).
///
/// # Errors
///
/// Each byte value must be a valid UTF-8 code point.
/// If an invalid sequence is found, then this function will return an error.
#[inline]
pub const fn from_utf8(v: Vec<u8, N>) -> Result<Self, (Utf8Error, Vec<u8, N>)> {
if let Err(e) = str::from_utf8(v.as_slice()) {
let i = e.valid_up_to();
let c = unsafe { *v.as_ptr().add(i) };
return Err((Utf8Error { value: c, index: i }, v));
}
// SAFETY: `s` has been tested to only contain
// valid octets.
let this = unsafe { Self::from_utf8_unchecked(v) };
Ok(this)
}
/// Unsafely constructs a new string from UTF-8 octets.
///
/// # Safety
///
/// Each byte value of the vector must be a valid UTF-8 code point.
/// The behaviour of a programme that passes invalid values to this function is undefined.
#[inline]
#[must_use]
pub const unsafe fn from_utf8_unchecked(v: Vec<u8, N>) -> Self {
let (mut buf, len) = v.into_raw_parts();
// Zero-initialise bytes that may be uninitialised.
unsafe {
let dst = buf.as_mut_ptr().add(len);
let count = N - len;
write_bytes(dst, 0x00, count);
}
// SAFETY: We can safely transmute here as
// `MaybeUninit<u8>` is transparent to `u8` and
// we have initialised the remaining bytes.
let buf = unsafe { buf.as_ptr().cast::<[u8; N]>().read() };
// SAFETY: `Vec::into_raw_parts` guarantees that
// the returned length is not greater than `N`.
unsafe { Self::from_raw_parts(buf, len) }
}
/// Constructs a size-constrained string from raw parts.
///
/// The provided parts are not tested in any way.
///
/// # Safety
///
/// The value of `len` may not exceed that of `N`.
/// Additionally, the octets in `buf` (from index zero up to the value of `len`) must be valid UTF-8 codepoints.
///
/// If any of these requirements are violated, behaviour is undefined.
#[inline(always)]
#[must_use]
pub const unsafe fn from_raw_parts(buf: [u8; N], len: usize) -> Self {
debug_assert!(len <= N, "cannot construct string that is longer than its capacity");
Self { len, buf }
}
/// Returns the current length of the string.
///
/// Remember that this value only denotes the octet count and **not** the amount of characters, graphemes, etc.
#[inline(always)]
#[must_use]
pub const fn len(&self) -> usize {
self.0.len()
self.len
}
/// Checks if the string is empty, i.e. no characters are contained.
#[inline(always)]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
self.len() == 0x0
}
/// Checks if the string is full, i.e. it cannot hold any more characters.
/// Checks if specified index is on the boundary of a character.
///
/// In this case, character is defined as a set of one to four UTF-8 octets that represent a Unicode code point (specifically a Unicode scalar).
#[inline(always)]
#[must_use]
pub const fn is_full(&self) -> bool {
self.0.is_full()
pub fn is_char_boundary(&self, index: usize) -> bool {
// TODO: Mark with `const`.
self.as_str().is_char_boundary(index)
}
/// Gets a pointer to the first octet.
#[inline(always)]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
self.buf.as_ptr()
}
/// Gets a mutable pointer to the first octet.
///
#[inline(always)]
#[must_use]
pub const fn as_mut_ptr(&mut self) -> *mut u8 {
self.buf.as_mut_ptr()
}
/// Borrows the string as a byte slice.
///
/// The range of the returned slice only includes characters that are "used."
#[inline(always)]
#[must_use]
pub const fn as_bytes(&self) -> &[u8] {
// We need to use `from_raw_parts` to mark this
// function `const`.
let ptr = self.as_ptr();
let len = self.len();
unsafe { slice::from_raw_parts(ptr, len) }
}
/// Borrows the string as a mutable byte slice.
///
/// The range of the returned slice only includes characters that are "used."
///
/// # Safety
///
/// Writes to the returned slice must not contain invalid UTF-8 octets.
#[inline(always)]
#[must_use]
pub const unsafe fn as_mut_bytes(&mut self) -> &mut [u8] {
let ptr = self.as_mut_ptr();
let len = self.len();
unsafe { slice::from_raw_parts_mut(ptr, len) }
}
/// Borrows the string as a string slice.
///
/// The range of the returned slice only includes characters that are "used."
#[inline(always)]
#[must_use]
pub const fn as_str(&self) -> &str {
// SAFETY: We guarantee that all octets are always
// valid UTF-8 code points.
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
/// Mutably borrows the string as a string slice.
///
/// The range of the returned slice only includes characters that are "used."
#[inline(always)]
#[must_use]
pub const fn as_mut_str(&mut self) -> &mut str {
unsafe {
let ptr = self.as_mut_ptr();
let len = self.len();
let data = slice::from_raw_parts_mut(ptr, len);
core::str::from_utf8_unchecked_mut(data)
}
}
/// Destructs the provided string into its raw parts.
///
/// The returned parts are valid to pass back to [`from_raw_parts`](Self::from_raw_parts).
///
/// The returned byte array is guaranteed to be fully initialised.
/// However, only octets up to an index of [`len`](Self::len) are also guaranteed to be valid UTF-8 codepoints.
#[inline(always)]
#[must_use]
pub const fn into_raw_parts(self) -> ([u8; N], usize) {
let Self { buf, len } = self;
(buf, len)
}
/// Converts the string into a vector of bytes.
///
/// The underlying memory of the string is completely reused.
#[inline(always)]
#[must_use]
pub const fn into_bytes(self) -> Vec<u8, N> {
let (buf, len) = self.into_raw_parts();
// SAFETY: `MaybeUninit<u8>` is transparent to `u8`.
let buf = unsafe { (&raw const buf).cast::<[MaybeUninit<u8>; N]>().read() };
unsafe { Vec::from_raw_parts(buf, len) }
}
/// Converts the size-constrained string into a boxed string slice.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[inline(always)]
#[must_use]
pub fn into_boxed_str(self) -> Box<str> {
let v = self.into_bytes();
unsafe { alloc::str::from_boxed_utf8_unchecked(v.into_boxed_slice()) }
}
/// Converts the size-constrained string into a dynamic string.
///
/// The capacity of the resulting [`alloc::string::String`] object is equal to the value of `N`.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[inline(always)]
#[must_use]
pub fn into_string(self) -> alloc::string::String {
self.into_boxed_str().into_string()
}
}
impl<const N: usize> AsMut<str> for String<N> {
#[inline(always)]
fn as_mut(&mut self) -> &mut str {
self.as_mut_str()
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<const N: usize> AsRef<OsStr> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &OsStr {
self.as_str().as_ref()
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<const N: usize> AsRef<Path> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &Path {
self.as_str().as_ref()
}
}
impl<const N: usize> AsRef<str> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> AsRef<[u8]> for String<N> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const N: usize> Borrow<str> for String<N> {
#[inline(always)]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> BorrowMut<str> for String<N> {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut str {
self.as_mut_str()
}
}
impl<const N: usize> Deref for String<N> {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<const N: usize> Default for String<N> {
#[inline(always)]
fn default() -> Self {
let buf = [Default::default(); N];
let len = 0x0;
unsafe { Self::from_raw_parts(buf, len) }
}
}
impl<const N: usize> DerefMut for String<N> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_str()
}
}
@ -114,6 +437,24 @@ impl<const N: usize> Debug for String<N> {
}
}
impl<const N: usize> Decode for String<N> {
type Error = CollectionDecodeError<LengthError, Utf8Error>;
#[inline]
fn decode(input: &mut decode::Input) -> Result<Self, Self::Error> {
let v = Vec::<u8, N>::decode(input)
.map_err(|e| {
let CollectionDecodeError::BadLength(e) = e;
CollectionDecodeError::BadLength(e)
})?;
Self::from_utf8(v)
.map_err(|(e, ..)| CollectionDecodeError::BadItem(e))
}
}
impl<const N: usize> DecodeBorrowed<str> for String<N> { }
impl<const N: usize> Display for String<N> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
@ -121,6 +462,17 @@ impl<const N: usize> Display for String<N> {
}
}
impl<const N: usize> Encode for String<N> {
type Error = <str as Encode>::Error;
#[inline(always)]
fn encode(&self, output: &mut encode::Output) -> Result<(), Self::Error> {
self.as_str().encode(output)
}
}
impl<const N: usize> Eq for String<N> { }
impl<const N: usize> FromIterator<char> for String<N> {
#[inline]
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
@ -147,6 +499,15 @@ impl<const N: usize> FromIterator<char> for String<N> {
}
}
impl<const N: usize> FromStr for String<N> {
type Err = LengthError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl<const N: usize> Hash for String<N> {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
@ -169,3 +530,125 @@ impl<I: SliceIndex<str>, const N: usize> IndexMut<I> for String<N> {
self.get_mut(index).unwrap()
}
}
impl<const N: usize> Ord for String<N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<const N: usize, const M: usize> PartialEq<String<M>> for String<N> {
#[inline(always)]
fn eq(&self, other: &String<M>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> PartialEq<str> for String<N> {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<const N: usize> PartialEq<&str> for String<N> {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialEq<alloc::string::String> for String<N> {
#[inline(always)]
fn eq(&self, other: &alloc::string::String) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize, const M: usize> PartialOrd<String<M>> for String<N> {
#[inline(always)]
fn partial_cmp(&self, other: &String<M>) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl<const N: usize> SizedEncode for String<N> {
const MAX_ENCODED_SIZE: usize =
usize::MAX_ENCODED_SIZE
+ u8::MAX_ENCODED_SIZE * N;
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<const N: usize> ToSocketAddrs for String<N> {
type Iter = <str as ToSocketAddrs>::Iter;
#[inline(always)]
fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
self.as_str().to_socket_addrs()
}
}
impl<const N: usize> TryFrom<char> for String<N> {
type Error = <Self as FromStr>::Err;
#[inline(always)]
fn try_from(value: char) -> Result<Self, Self::Error> {
let mut buf = [0x00; 0x4];
let s = value.encode_utf8(&mut buf);
s.parse()
}
}
impl<const N: usize> TryFrom<&str> for String<N> {
type Error = <Self as FromStr>::Err;
#[inline(always)]
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> TryFrom<alloc::string::String> for String<N> {
type Error = <Self as FromStr>::Err;
#[inline(always)]
fn try_from(value: alloc::string::String) -> Result<Self, Self::Error> {
Self::new(&value)
}
}
/// See [`into_boxed_str`](String::into_boxed_str).
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> From<String<N>> for Box<str> {
#[inline(always)]
fn from(value: String<N>) -> Self {
value.into_boxed_str()
}
}
/// See [`into_string`](String::into_string).
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> From<String<N>> for alloc::string::String {
#[inline(always)]
fn from(value: String<N>) -> Self {
value.into_string()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialEq<String<N>> for alloc::string::String {
#[inline(always)]
fn eq(&self, other: &String<N>) -> bool {
self.as_str() == other.as_str()
}
}

View file

@ -0,0 +1,83 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::cmp::Ordering;
use oct::error::Utf8Error;
use oct::string::String;
use oct::vec::Vec;
#[test]
fn test_string() {
let s: String::<0x4> = "hello world".chars().collect();
assert_eq!(s, "hell")
}
#[test]
fn test_string_size() {
let s0 = String::<0x0C>::new("Hello there!").unwrap();
let s1 = String::<0x12>::new("MEIN_GRO\u{1E9E}_GOTT").unwrap();
let s2 = String::<0x05>::new("Hello").unwrap();
assert_eq!(s0.partial_cmp(&s0), Some(Ordering::Equal));
assert_eq!(s0.partial_cmp(&s1), Some(Ordering::Less));
assert_eq!(s0.partial_cmp(&s2), Some(Ordering::Greater));
assert_eq!(s1.partial_cmp(&s0), Some(Ordering::Greater));
assert_eq!(s1.partial_cmp(&s1), Some(Ordering::Equal));
assert_eq!(s1.partial_cmp(&s2), Some(Ordering::Greater));
assert_eq!(s2.partial_cmp(&s0), Some(Ordering::Less));
assert_eq!(s2.partial_cmp(&s1), Some(Ordering::Less));
assert_eq!(s2.partial_cmp(&s2), Some(Ordering::Equal));
assert_eq!(s0, "Hello there!");
assert_eq!(s1, "MEIN_GRO\u{1E9E}_GOTT");
assert_eq!(s2, "Hello");
}
#[test]
fn test_string_from_utf8() {
macro_rules! test_utf8 {
{
len: $len:literal,
utf8: $utf8:expr,
result: $result:pat$(,)?
} => {{
let utf8 = core::borrow::Borrow::<[u8]>::borrow($utf8);
assert!(matches!(
String::<$len>::from_utf8(Vec::new(utf8).unwrap()),
$result,
));
}};
}
test_utf8!(
len: 0x3,
utf8: b"A\xF7c",
result: Err((Utf8Error { value: 0xF7, index: 0x1 }, ..)),
);
test_utf8!(
len: 0x4,
utf8: b"A\xC3\xBCc",
result: Ok(..),
);
test_utf8!(
len: 0x4,
utf8: b"20\x20\xAC",
result: Err((Utf8Error { value: 0xAC, index: 0x3 }, ..)),
);
test_utf8!(
len: 0x5,
utf8: b"20\xE2\x82\xAC",
result: Ok(..),
);
}

View file

@ -1,82 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use oct::error::{StringError, Utf8Error};
use oct::string::String;
use std::cmp::Ordering;
#[test]
fn test_string() {
let s: String::<0x4> = "hello world".chars().collect();
assert_eq!(s, "hell")
}
#[test]
fn test_string_size() {
let string0 = String::<0x0C>::try_from("Hello there!").unwrap();
let string1 = String::<0x12>::try_from("MEIN_GRO\u{1E9E}_GOTT").unwrap();
let string2 = String::<0x05>::try_from("Hello").unwrap();
assert_eq!(string0.partial_cmp(&string0), Some(Ordering::Equal));
assert_eq!(string0.partial_cmp(&string1), Some(Ordering::Less));
assert_eq!(string0.partial_cmp(&string2), Some(Ordering::Greater));
assert_eq!(string1.partial_cmp(&string0), Some(Ordering::Greater));
assert_eq!(string1.partial_cmp(&string1), Some(Ordering::Equal));
assert_eq!(string1.partial_cmp(&string2), Some(Ordering::Greater));
assert_eq!(string2.partial_cmp(&string0), Some(Ordering::Less));
assert_eq!(string2.partial_cmp(&string1), Some(Ordering::Less));
assert_eq!(string2.partial_cmp(&string2), Some(Ordering::Equal));
assert_eq!(string0, "Hello there!");
assert_eq!(string1, "MEIN_GRO\u{1E9E}_GOTT");
assert_eq!(string2, "Hello");
}
#[test]
fn test_string_from_utf8() {
macro_rules! test_utf8 {
{
len: $len:literal,
utf8: $utf8:expr,
result: $result:pat$(,)?
} => {{
let utf8: &[u8] = $utf8.as_ref();
assert!(matches!(
String::<$len>::from_utf8(utf8),
$result,
));
}};
}
test_utf8!(
len: 0x3,
utf8: b"A\xF7c",
result: Err(StringError::BadUtf8(Utf8Error { value: 0xF7, index: 0x1 })),
);
test_utf8!(
len: 0x4,
utf8: "A\u{00F7}c",
result: Ok(..),
);
test_utf8!(
len: 0x4,
utf8: b"20\x20\xAC",
result: Err(StringError::BadUtf8(Utf8Error { value: 0xAC, index: 0x3 })),
);
test_utf8!(
len: 0x5,
utf8: "20\u{20AC}",
result: Ok(..),
);
}

View file

@ -7,7 +7,7 @@
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod tests;
mod test;
use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator};
use core::mem::MaybeUninit;
@ -20,19 +20,19 @@ use core::slice;
/// When just borrowing such vectors, the standard library's <code>core::slice::{[Iter](core::slice::Iter), [IterMut](core::slice::IterMut)}</code> types are used instead.
#[must_use]
pub struct IntoIter<T, const N: usize> {
buf: [MaybeUninit<T>; N],
pos: usize,
len: usize,
pos: usize,
buf: [MaybeUninit<T>; N],
}
impl<T, const N: usize> IntoIter<T, N> {
/// Constructs a new, fixed-size iterator.
/// Constructs a new, size-constrained iterator.
#[inline(always)]
pub(crate) const unsafe fn new(buf: [MaybeUninit<T>; N], len: usize) -> Self {
debug_assert!(len <= N, "cannot construct iterator longer than its capacity");
Self { buf, pos: 0x0, len }
Self { len, pos: 0x0, buf }
}
/// Gets a slice of the remaining elements.
@ -79,7 +79,7 @@ impl<T, const N: usize> AsRef<[T]> for IntoIter<T, N> {
impl<T: Clone, const N: usize> Clone for IntoIter<T, N> {
#[inline]
fn clone(&self) -> Self {
let mut buf = [const { MaybeUninit::<T>::uninit() };N];
let mut buf = [const { MaybeUninit::<T>::uninit() }; N];
let Self { pos, len, .. } = *self;
let start = pos;
@ -95,7 +95,7 @@ impl<T: Clone, const N: usize> Clone for IntoIter<T, N> {
}
}
Self { buf, pos, len }
Self { len, pos, buf }
}
}

View file

@ -6,9 +6,9 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use core::str::FromStr;
use oct::string::String;
use oct::vec::Vec;
use std::str::FromStr;
#[test]
fn test_vec_iter_clone() {

View file

@ -1,80 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::vec::Vec;
use core::cmp::Ordering;
impl<T: Eq, const N: usize> Eq for Vec<T, N> { }
impl<T: Ord, const N: usize> Ord for Vec<T, N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_slice().cmp(other.as_slice())
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<Vec<U, M>> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &Vec<U, M>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<[U; M]> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &[U; M]) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<&[U]> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &&[U]) -> bool {
self.as_slice() == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<alloc::vec::Vec<U>> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &alloc::vec::Vec<U>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<Vec<T, M>> for Vec<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &Vec<T, M>) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<[T; M]> for Vec<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &[T; M]) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
impl<T: PartialOrd, const N: usize> PartialOrd<&[T]> for Vec<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &&[T]) -> Option<Ordering> {
self.as_slice().partial_cmp(*other)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialOrd, const N: usize> PartialOrd<alloc::vec::Vec<T>> for Vec<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &alloc::vec::Vec<T>) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}

View file

@ -1,56 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::vec::Vec;
use crate::decode::{Decode, DecodeBorrowed, Input};
use crate::encode::{Encode, Output, SizedEncode};
use crate::error::{CollectionDecodeError, ItemDecodeError, LengthError};
use core::mem::MaybeUninit;
impl<T: Decode, const N: usize> Decode for Vec<T, N> {
type Error = CollectionDecodeError<LengthError, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let len = Decode::decode(input).unwrap();
if len > N {
return Err(CollectionDecodeError::BadLength(LengthError {
remaining: N,
count: len,
}));
}
let mut buf = [const { MaybeUninit::<T>::uninit() }; N];
for (i, slot) in buf[..len].iter_mut().enumerate() {
let v = Decode::decode(input)
.map_err(|e| CollectionDecodeError::BadItem(ItemDecodeError { index: i, error: e }))?;
slot.write(v);
}
let this = unsafe { Self::from_raw_parts(buf, len) };
Ok(this)
}
}
impl<T: Decode, const N: usize> DecodeBorrowed<[T]> for Vec<T, N> { }
impl<T: Encode, const N: usize> Encode for Vec<T, N> {
type Error = <[T] as Encode>::Error;
#[inline(always)]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
self.as_slice().encode(output)
}
}
impl<T: SizedEncode, const N: usize> SizedEncode for Vec<T, N> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N;
}

View file

@ -1,226 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::vec::Vec;
use crate::error::LengthError;
use core::borrow::{Borrow, BorrowMut};
use core::mem::{ManuallyDrop, MaybeUninit};
use core::ops::{Deref, DerefMut};
use core::ptr::copy_nonoverlapping;
use core::slice;
#[cfg(feature = "alloc")]
use {
alloc::alloc::alloc,
alloc::boxed::Box,
core::alloc::Layout,
};
impl<T, const N: usize> Vec<T, N> {
/// Constructs a fixed-size vector from raw parts.
///
/// The provided parts are not tested in any way.
///
/// # Safety
///
/// The value of `len` may not exceed that of `N`.
/// Additionally, all elements of `buf` in the range specified by `len` must be initialised.
///
/// If any of these requirements are violated, behaviour is undefined.
#[inline(always)]
#[must_use]
pub const unsafe fn from_raw_parts(buf: [MaybeUninit<T>; N], len: usize) -> Self {
debug_assert!(len <= N, "cannot construct vector longer than its capacity");
Self { buf, len }
}
/// Gets a pointer to the first element.
///
/// The pointed-to element may not necessarily be initialised.
/// See [`len`](Self::len) for more information.
#[inline(always)]
#[must_use]
pub const fn as_ptr(&self) -> *const T {
self.buf.as_ptr().cast()
}
/// Gets a mutable pointer to the first element.
///
/// The pointed-to element may not necessarily be initialised.
/// See [`len`](Self::len) for more information.
#[inline(always)]
#[must_use]
pub const fn as_mut_ptr(&mut self) -> *mut T {
self.buf.as_mut_ptr().cast()
}
/// Borrows the vector as a slice.
///
/// The range of the returned slice only includes the elements specified by [`len`](Self::len).
#[inline(always)]
#[must_use]
pub const fn as_slice(&self) -> &[T] {
let ptr = self.as_ptr();
let len = self.len();
unsafe { slice::from_raw_parts(ptr, len) }
}
/// Borrows the vector as a mutable slice.
///
/// The range of the returned slice only includes the elements specified by [`len`](Self::len).
#[inline(always)]
#[must_use]
pub const fn as_mut_slice(&mut self) -> &mut [T] {
let ptr = self.as_mut_ptr();
let len = self.len();
unsafe { slice::from_raw_parts_mut(ptr, len) }
}
/// Destructs the vector into its raw parts.
///
/// The returned parts are valid to pass back to [`from_raw_parts`](Self::from_raw_parts).
#[inline(always)]
#[must_use]
pub const fn into_raw_parts(self) -> ([MaybeUninit<T>; N], usize) {
let this = ManuallyDrop::new(self);
unsafe {
// SAFETY: `ManuallyDrop<T>` is transparent to `T`.
// We also aren't dropping `this`, so we can safely
// move out of it.
let this = &*(&raw const this).cast::<Self>();
let buf = (&raw const this.buf).read();
let len = this.len;
(buf, len)
}
}
/// Converts the vector into a boxed slice.
///
/// The vector is reallocated using the global allocator.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[must_use]
pub fn into_boxed_slice(self) -> Box<[T]> {
let (buf, len) = self.into_raw_parts();
unsafe {
let layout = Layout::array::<T>(len).unwrap();
let ptr = alloc(layout).cast::<T>();
assert!(!ptr.is_null(), "allocation failed");
copy_nonoverlapping(buf.as_ptr().cast(), ptr, len);
let slice = core::ptr::slice_from_raw_parts_mut(ptr, len);
Box::from_raw(slice)
// `self.buf` is dropped without destructors being
// run.
}
}
/// Converts the vector into a dynamically-allocated vector.
///
/// The vector is reallocated using the global allocator.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[inline(always)]
#[must_use]
pub fn into_alloc_vec(self) -> alloc::vec::Vec<T> {
self.into_boxed_slice().into_vec()
}
}
impl<T, const N: usize> AsMut<[T]> for Vec<T, N> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T, const N: usize> AsRef<[T]> for Vec<T, N> {
#[inline(always)]
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> Borrow<[T]> for Vec<T, N> {
#[inline(always)]
fn borrow(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> BorrowMut<[T]> for Vec<T, N> {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T, const N: usize> Deref for Vec<T, N> {
type Target = [T];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for Vec<T, N> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
impl<T, const N: usize> From<[T; N]> for Vec<T, N> {
#[inline(always)]
fn from(value: [T; N]) -> Self {
unsafe {
let buf = value.as_ptr().cast::<[MaybeUninit<T>; N]>().read();
Self { buf, len: N }
}
}
}
impl<T: Copy, const N: usize> TryFrom<&[T]> for Vec<T, N> {
type Error = LengthError;
#[inline(always)]
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<Vec<T, N>> for Box<[T]> {
#[inline(always)]
fn from(value: Vec<T, N>) -> Self {
value.into_boxed_slice()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<Vec<T, N>> for alloc::vec::Vec<T> {
#[inline(always)]
fn from(value: Vec<T, N>) -> Self {
value.into_alloc_vec()
}
}

View file

@ -1,66 +0,0 @@
// Copyright 2024-2025 Gabriel Bjørnager Jensen.
//
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, you
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
use crate::vec::{IntoIter, Vec};
use core::mem::MaybeUninit;
use core::slice;
impl<T, const N: usize> FromIterator<T> for Vec<T, N> {
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut iter = iter.into_iter();
let mut buf = [const { MaybeUninit::<T>::uninit() };N];
let mut len = 0x0;
for item in &mut buf {
let Some(value) = iter.next() else { break };
item.write(value);
len += 0x1;
}
Self { buf, len }
}
}
impl<T, const N: usize> IntoIterator for Vec<T, N> {
type Item = T;
type IntoIter = IntoIter<T, N>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
let (buf, len) = self.into_raw_parts();
unsafe { IntoIter::new(buf, len) }
}
}
impl<'a, T, const N: usize> IntoIterator for &'a Vec<T, N> {
type Item = &'a T;
type IntoIter = slice::Iter<'a, T>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T, const N: usize> IntoIterator for &'a mut Vec<T, N> {
type Item = &'a mut T;
type IntoIter = slice::IterMut<'a, T>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}

View file

@ -7,28 +7,28 @@
// <https://mozilla.org/MPL/2.0/>.
#[cfg(test)]
mod tests;
mod test;
use crate::error::LengthError;
use crate::decode::{self, Decode, DecodeBorrowed};
use crate::encode::{self, Encode, SizedEncode};
use crate::error::{CollectionDecodeError, ItemDecodeError, LengthError};
use crate::vec::IntoIter;
use core::borrow::{Borrow, BorrowMut};
use core::cmp::Ordering;
use core::fmt::{self, Debug, Formatter};
use core::hash::{Hash, Hasher};
use core::mem::MaybeUninit;
use core::ops::{Index, IndexMut};
use core::mem::{ManuallyDrop, MaybeUninit};
use core::ops::{Deref, DerefMut, Index, IndexMut};
use core::ptr::{copy_nonoverlapping, drop_in_place, null, null_mut};
use core::slice::SliceIndex;
use core::slice::{self, SliceIndex};
// Encode/decode facilities:
mod code;
// Conversion facilities:
mod conv;
// Comparison facilities:
mod cmp;
// Iterator facilities:
mod iter;
#[cfg(feature = "alloc")]
use {
alloc::alloc::alloc,
alloc::boxed::Box,
core::alloc::Layout,
};
/// Vector container with maximum length.
///
@ -45,21 +45,21 @@ mod iter;
/// ```rust
/// use oct::vec::Vec;
///
/// let vec0 = Vec::<u8, 0x4>::try_from([0x3].as_slice()).unwrap();
/// let vec1 = Vec::<u8, 0x4>::try_from([0x3, 0x2].as_slice()).unwrap();
/// let vec2 = Vec::<u8, 0x4>::try_from([0x3, 0x2, 0x4].as_slice()).unwrap();
/// let vec3 = Vec::<u8, 0x4>::try_from([0x3, 0x2, 0x4, 0x3].as_slice()).unwrap();
/// let v0 = Vec::<u8, 0x4>::try_from([0x3].as_slice()).unwrap();
/// let v1 = Vec::<u8, 0x4>::try_from([0x3, 0x2].as_slice()).unwrap();
/// let v2 = Vec::<u8, 0x4>::try_from([0x3, 0x2, 0x4].as_slice()).unwrap();
/// let v3 = Vec::<u8, 0x4>::try_from([0x3, 0x2, 0x4, 0x3].as_slice()).unwrap();
///
/// assert_eq!(size_of_val(&vec0), size_of_val(&vec1));
/// assert_eq!(size_of_val(&vec0), size_of_val(&vec2));
/// assert_eq!(size_of_val(&vec0), size_of_val(&vec3));
/// assert_eq!(size_of_val(&vec1), size_of_val(&vec2));
/// assert_eq!(size_of_val(&vec1), size_of_val(&vec3));
/// assert_eq!(size_of_val(&vec2), size_of_val(&vec3));
/// assert_eq!(size_of_val(&v0), size_of_val(&v1));
/// assert_eq!(size_of_val(&v0), size_of_val(&v2));
/// assert_eq!(size_of_val(&v0), size_of_val(&v3));
/// assert_eq!(size_of_val(&v1), size_of_val(&v2));
/// assert_eq!(size_of_val(&v1), size_of_val(&v3));
/// assert_eq!(size_of_val(&v2), size_of_val(&v3));
/// ```
pub struct Vec<T, const N: usize> {
buf: [MaybeUninit<T>; N],
len: usize,
buf: [MaybeUninit<T>; N],
}
impl<T, const N: usize> Vec<T, N> {
@ -81,18 +81,54 @@ impl<T, const N: usize> Vec<T, N> {
});
}
let mut buf = [const { MaybeUninit::<T>::uninit() };N];
// SAFETY: We have checked that the length is with-
// in bounds.
let this = unsafe { Self::new_unchecked(data) };
Ok(this)
}
let this = unsafe {
/// Constructs a new slice from existing data without checking bounds.
///
/// # Safety
///
/// The entirety of `data` must be able to fit into an array of `N` elements.
#[inline(always)]
pub const unsafe fn new_unchecked(data: &[T]) -> Self
where
T: Copy,
{
let len = data.len();
let mut buf = [const { MaybeUninit::<T>::uninit() }; N];
debug_assert!(len <= N, "cannot construct vector from slice that is longer");
unsafe {
let src = data.as_ptr();
let dst = buf.as_mut_ptr().cast();
copy_nonoverlapping(src, dst, len);
}
Self::from_raw_parts(buf, len)
};
// SAFETY: The relevant elements have been initialised and `len`
unsafe { Self::from_raw_parts(buf, len) }
}
Ok(this)
/// Constructs a size-constrained vector from raw parts.
///
/// The provided parts are not tested in any way.
///
/// # Safety
///
/// The value of `len` may not exceed that of `N`.
/// Additionally, all elements of `buf` in the range specified by `len` must be initialised.
///
/// If any of these requirements are violated, behaviour is undefined.
#[inline(always)]
#[must_use]
pub const unsafe fn from_raw_parts(buf: [MaybeUninit<T>; N], len: usize) -> Self {
debug_assert!(len <= N, "cannot construct vector longer than its capacity");
Self { len, buf }
}
/// Copies elements from a slice.
@ -120,7 +156,7 @@ impl<T, const N: usize> Vec<T, N> {
}
}
/// Generates a sized slice referencing the elements of `self`.
/// Generates a vector referencing the elements of `self`.
#[inline]
#[must_use]
pub const fn each_ref(&self) -> Vec<&T, N> {
@ -147,7 +183,7 @@ impl<T, const N: usize> Vec<T, N> {
unsafe { Vec::from_raw_parts(buf, len) }
}
/// Generates a sized slice mutably referencing the elements of `self`.
/// Generates a vector mutably referencing the elements of `self`.
#[inline]
#[must_use]
pub const fn each_mut(&mut self) -> Vec<&mut T, N> {
@ -214,7 +250,7 @@ impl<T, const N: usize> Vec<T, N> {
/// Returns the length of the vector.
///
/// This value may necessarily be smaller than `N`.
/// This value denotes the current amount of elements contained in the vector, which may be any value between zero and `N` (inclusive).
#[inline(always)]
#[must_use]
pub const fn len(&self) -> usize {
@ -230,11 +266,133 @@ impl<T, const N: usize> Vec<T, N> {
self.len() == 0x0
}
/// Checks if the vector is full, i.e. it cannot hold any more elements.
/// Gets a pointer to the first element.
///
/// The pointed-to element may not necessarily be initialised.
/// See [`len`](Self::len) for more information.
#[inline(always)]
#[must_use]
pub const fn is_full(&self) -> bool {
self.len() == N
pub const fn as_ptr(&self) -> *const T {
self.buf.as_ptr().cast()
}
/// Gets a mutable pointer to the first element.
///
/// The pointed-to element may not necessarily be initialised.
/// See [`len`](Self::len) for more information.
#[inline(always)]
#[must_use]
pub const fn as_mut_ptr(&mut self) -> *mut T {
self.buf.as_mut_ptr().cast()
}
/// Borrows the vector as a slice.
///
/// The range of the returned slice only includes the elements specified by [`len`](Self::len).
#[inline(always)]
#[must_use]
pub const fn as_slice(&self) -> &[T] {
let ptr = self.as_ptr();
let len = self.len();
unsafe { slice::from_raw_parts(ptr, len) }
}
/// Borrows the vector as a mutable slice.
///
/// The range of the returned slice only includes the elements specified by [`len`](Self::len).
#[inline(always)]
#[must_use]
pub const fn as_mut_slice(&mut self) -> &mut [T] {
let ptr = self.as_mut_ptr();
let len = self.len();
unsafe { slice::from_raw_parts_mut(ptr, len) }
}
/// Destructs the vector into its raw parts.
///
/// The returned parts are valid to pass back to [`from_raw_parts`](Self::from_raw_parts).
#[inline(always)]
#[must_use]
pub const fn into_raw_parts(self) -> ([MaybeUninit<T>; N], usize) {
let this = ManuallyDrop::new(self);
unsafe {
// SAFETY: `ManuallyDrop<T>` is transparent to `T`.
// We also aren't dropping `this`, so we can safely
// move out of it.
let this = &*(&raw const this).cast::<Self>();
let buf = (&raw const this.buf).read();
let len = this.len;
(buf, len)
}
}
/// Converts the vector into a boxed slice.
///
/// The vector is reallocated using the global allocator.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[must_use]
pub fn into_boxed_slice(self) -> Box<[T]> {
let (buf, len) = self.into_raw_parts();
unsafe {
let layout = Layout::array::<T>(len).unwrap();
let ptr = alloc(layout).cast::<T>();
assert!(!ptr.is_null(), "allocation failed");
copy_nonoverlapping(buf.as_ptr().cast(), ptr, len);
let slice = core::ptr::slice_from_raw_parts_mut(ptr, len);
Box::from_raw(slice)
// `self.buf` is dropped without destructors being
// run.
}
}
/// Converts the vector into a dynamically-allocated vector.
///
/// The vector is reallocated using the global allocator.
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
#[inline(always)]
#[must_use]
pub fn into_alloc_vec(self) -> alloc::vec::Vec<T> {
self.into_boxed_slice().into_vec()
}
}
impl<T, const N: usize> AsMut<[T]> for Vec<T, N> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T, const N: usize> AsRef<[T]> for Vec<T, N> {
#[inline(always)]
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> Borrow<[T]> for Vec<T, N> {
#[inline(always)]
fn borrow(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> BorrowMut<[T]> for Vec<T, N> {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
@ -261,6 +419,35 @@ impl<T: Debug, const N: usize> Debug for Vec<T, N> {
}
}
impl<T: Decode, const N: usize> Decode for Vec<T, N> {
type Error = CollectionDecodeError<LengthError, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(input: &mut decode::Input) -> Result<Self, Self::Error> {
let len = Decode::decode(input).unwrap();
if len > N {
return Err(CollectionDecodeError::BadLength(LengthError {
remaining: N,
count: len,
}));
}
let mut buf = [const { MaybeUninit::<T>::uninit() }; N];
for (i, slot) in buf[..len].iter_mut().enumerate() {
let v = Decode::decode(input)
.map_err(|e| CollectionDecodeError::BadItem(ItemDecodeError { index: i, error: e }))?;
slot.write(v);
}
let this = unsafe { Self::from_raw_parts(buf, len) };
Ok(this)
}
}
impl<T: Decode, const N: usize> DecodeBorrowed<[T]> for Vec<T, N> { }
impl<T, const N: usize> Default for Vec<T, N> {
#[inline(always)]
fn default() -> Self {
@ -273,6 +460,22 @@ impl<T, const N: usize> Default for Vec<T, N> {
}
}
impl<T, const N: usize> Deref for Vec<T, N> {
type Target = [T];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for Vec<T, N> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
impl<T, const N: usize> Drop for Vec<T, N> {
#[inline(always)]
fn drop(&mut self) {
@ -287,6 +490,47 @@ impl<T, const N: usize> Drop for Vec<T, N> {
}
}
impl<T: Encode, const N: usize> Encode for Vec<T, N> {
type Error = <[T] as Encode>::Error;
#[inline(always)]
fn encode(&self, output: &mut encode::Output) -> Result<(), Self::Error> {
self.as_slice().encode(output)
}
}
impl<T: Eq, const N: usize> Eq for Vec<T, N> { }
impl<T, const N: usize> From<[T; N]> for Vec<T, N> {
#[inline(always)]
fn from(value: [T; N]) -> Self {
unsafe {
let buf = value.as_ptr().cast::<[MaybeUninit<T>; N]>().read();
Self { buf, len: N }
}
}
}
impl<T, const N: usize> FromIterator<T> for Vec<T, N> {
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut iter = iter.into_iter();
let mut buf = [const { MaybeUninit::<T>::uninit() }; N];
let mut len = 0x0;
for item in &mut buf {
let Some(value) = iter.next() else { break };
item.write(value);
len += 0x1;
}
Self { len, buf }
}
}
impl<T: Hash, const N: usize> Hash for Vec<T, N> {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
@ -311,3 +555,129 @@ impl<T, I: SliceIndex<[T]>, const N: usize> IndexMut<I> for Vec<T, N> {
self.get_mut(index).unwrap()
}
}
impl<T, const N: usize> IntoIterator for Vec<T, N> {
type Item = T;
type IntoIter = IntoIter<T, N>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
let (buf, len) = self.into_raw_parts();
unsafe { IntoIter::new(buf, len) }
}
}
impl<'a, T, const N: usize> IntoIterator for &'a Vec<T, N> {
type Item = &'a T;
type IntoIter = slice::Iter<'a, T>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T, const N: usize> IntoIterator for &'a mut Vec<T, N> {
type Item = &'a mut T;
type IntoIter = slice::IterMut<'a, T>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<T: Ord, const N: usize> Ord for Vec<T, N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_slice().cmp(other.as_slice())
}
}
impl<T: PartialEq<U>, U, const N: usize, const M: usize> PartialEq<Vec<U, M>> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &Vec<U, M>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialEq<U>, U, const N: usize, const M: usize> PartialEq<[U; M]> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &[U; M]) -> bool {
self == other.as_slice()
}
}
impl<T: PartialEq<U>, U, const N: usize> PartialEq<[U]> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &[U]) -> bool {
self.as_slice() == other
}
}
impl<T: PartialEq<U>, U, const N: usize> PartialEq<&[U]> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &&[U]) -> bool {
self == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialEq<U>, U, const N: usize> PartialEq<alloc::vec::Vec<U>> for Vec<T, N> {
#[inline(always)]
fn eq(&self, other: &alloc::vec::Vec<U>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<Vec<T, M>> for Vec<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &Vec<T, M>) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
impl<T: SizedEncode, const N: usize> SizedEncode for Vec<T, N> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N;
}
impl<T: Copy, const N: usize> TryFrom<&[T]> for Vec<T, N> {
type Error = LengthError;
#[inline(always)]
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<Vec<T, N>> for Box<[T]> {
#[inline(always)]
fn from(value: Vec<T, N>) -> Self {
value.into_boxed_slice()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<Vec<T, N>> for alloc::vec::Vec<T> {
#[inline(always)]
fn from(value: Vec<T, N>) -> Self {
value.into_alloc_vec()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialEq<U>, U, const N: usize> PartialEq<Vec<U, N>> for alloc::vec::Vec<T> {
#[inline(always)]
fn eq(&self, other: &Vec<U, N>) -> bool {
self.as_slice() == other.as_slice()
}
}