Clean up code; Again make 'bool' decoding fallible; Bring back 'BoolDecodeError' error; Add 'BadChar' and 'NonBool' variants to 'GenericDecodeError'; Fix 'GenericDecodeError' not implementing 'From<CharDecodeError>'; Implement 'From<BoolDecodeError>' for 'GenericDecodeError'; Fix '<Option as Decode>::Error' not being a form of 'EnumDecodeError'; Remove 'string' and 'vec' modules (including contents); Remove 'string' and 'vec' macros; Update readme; Fix '<{Option, Result, SocketAddr} as Encode>::Error' not being forms of 'EnumEncodeError';

This commit is contained in:
Gabriel Bjørnager Jensen 2025-04-04 17:54:28 +02:00
parent 40ff018433
commit ffb19c5a97
26 changed files with 231 additions and 2370 deletions

View file

@ -3,6 +3,20 @@
This is the changelog of [Oct](https://crates.io/crates/oct/). This is the changelog of [Oct](https://crates.io/crates/oct/).
See `README.md` for more information. See `README.md` for more information.
## 0.22.0
* Clean up code
* Again make `bool` decoding fallible
* Bring back `BoolDecodeError` error
* Add `BadChar` and `NonBool` variants to `GenericDecodeError`
* Fix `GenericDecodeError` not implementing `From<CharDecodeError>`
* Implement `From<BoolDecodeError>` for `GenericDecodeError`
* Fix `<Option as Decode>::Error` not being a form of `EnumDecodeError`
* Remove `string` and `vec` modules (including contents)
* Remove `string` and `vec` macros
* Update readme
* Fix `<{Option, Result, SocketAddr} as Encode>::Error` not being forms of `EnumEncodeError`
## 0.21.2 ## 0.21.2
* Update tests * Update tests

View file

@ -9,7 +9,7 @@ members = ["oct", "oct-benchmarks", "oct-macros"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.21.2" version = "0.22.0"
authors = ["Gabriel Bjørnager Jensen"] authors = ["Gabriel Bjørnager Jensen"]
readme = "README.md" readme = "README.md"
repository = "https://mandelbrot.dk/bjoernager/oct/" repository = "https://mandelbrot.dk/bjoernager/oct/"

View file

@ -197,7 +197,7 @@ The nightly toolchain is therefore always required when rendering them or or run
Oct does not accept source code contributions at the moment. Oct does not accept source code contributions at the moment.
This is a personal choice by the maintainer and may be undone in the future. This is a personal choice by the maintainer and may be undone in the future.
Do however feel free to open an issue on [GitLab](https://gitlab.com/bjoernager/oct/issues/) or [GitHub](https://github.com/bjoernager/oct/issues/) if you feel the need to express any concerns over the project. Do however feel free to open an issue on [GitLab](https://gitlab.com/bjoernager/oct/issues/), on [GitHub](https://github.com/bjoernager/oct/issues/), or on [`mandelbrot.dk`](https://mandelbrot.dk/bjoernager/oct/issues/) (if a member) if you feel the need to express any concerns over the project.
## Copyright & Licence ## Copyright & Licence

View file

@ -32,7 +32,7 @@ readme.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
oct = { path = "../oct", version = "0.21", features = ["proc-macro"]} oct = { path = "../oct", version = "0.22", features = ["proc-macro"]}
bincode = "2.0" bincode = "2.0"
rand = "0.9" rand = "0.9"

View file

@ -775,7 +775,7 @@ fn main() {
let mut stream = Input::new(&buf); let mut stream = Input::new(&buf);
for _ in 0x0..VALUE_COUNT { for _ in 0x0..VALUE_COUNT {
let Ok(_) = bool::decode(&mut stream); let _ = bool::decode(&mut stream);
} }
} }

View file

@ -29,18 +29,6 @@ pub fn sized_encode_enum(data: DataEnum, repr: Repr) -> TokenStream {
.collect(); .collect();
quote! { quote! {
const MAX_ENCODED_SIZE: usize = { const MAX_ENCODED_SIZE: usize = ::oct::enum_encoded_size!(#((#(#tys, )*), )*);
let mut total_size = 0x0usize;
let mut current_size = 0x0usize;
#(
current_size = 0x0 #(+ <#tys as ::oct::encode::SizedEncode>::MAX_ENCODED_SIZE)*;
if current_size > total_size { total_size = current_size };
)*
total_size
};
} }
} }

View file

@ -26,7 +26,7 @@ categories.workspace = true
all-features = true all-features = true
[dependencies] [dependencies]
oct-macros = { path = "../oct-macros", version = "0.21", optional = true} oct-macros = { path = "../oct-macros", version = "0.22", optional = true}
[features] [features]
default = ["alloc", "proc-macro", "std"] default = ["alloc", "proc-macro", "std"]

View file

@ -11,6 +11,7 @@ mod test;
use crate::__cold_path; use crate::__cold_path;
use crate::decode::{DecodeBorrowed, Input}; use crate::decode::{DecodeBorrowed, Input};
use crate::error::{ use crate::error::{
BoolDecodeError,
CharDecodeError, CharDecodeError,
CollectionDecodeError, CollectionDecodeError,
EnumDecodeError, EnumDecodeError,
@ -95,7 +96,7 @@ pub trait Decode: Sized {
/// # Panics /// # Panics
/// ///
/// If `input` unexpectedly terminates before a full encoding was read, then this method should panic. /// If `input` unexpectedly terminates before a full encoding was read, then this method should panic.
/// #[track_caller] #[track_caller]
fn decode(input: &mut Input) -> Result<Self, Self::Error>; fn decode(input: &mut Input) -> Result<Self, Self::Error>;
} }
@ -178,7 +179,7 @@ impl<T: Decode + Ord> Decode for BinaryHeap<T> {
} }
impl Decode for bool { impl Decode for bool {
type Error = Infallible; type Error = BoolDecodeError;
/// Lossily reinterprets a byte value as a boolean. /// Lossily reinterprets a byte value as a boolean.
/// ///
@ -188,6 +189,11 @@ impl Decode for bool {
fn decode(input: &mut Input) -> Result<Self, Self::Error> { fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(value) = u8::decode(input); let Ok(value) = u8::decode(input);
if value > 0x1 {
__cold_path();
return Err(BoolDecodeError { value });
}
let this = value != 0x0; let this = value != 0x0;
Ok(this) Ok(this)
} }
@ -297,12 +303,12 @@ impl Decode for char {
let Ok(code_point) = u32::decode(input); let Ok(code_point) = u32::decode(input);
match code_point { match code_point {
code_point @ (0x0000..=0xD7FF | 0xDE00..=0x10FFFF) => { 0x0000..=0xD7FF | 0xDE00..=0x10FFFF => {
let this = unsafe { Self::from_u32_unchecked(code_point) }; let this = unsafe { Self::from_u32_unchecked(code_point) };
Ok(this) Ok(this)
}, },
code_point => { _ => {
__cold_path(); __cold_path();
Err(CharDecodeError { code_point }) Err(CharDecodeError { code_point })
}, },
@ -452,14 +458,26 @@ impl Decode for IpAddr {
fn decode(input: &mut Input) -> Result<Self, Self::Error> { fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(discriminant) = u8::decode(input); let Ok(discriminant) = u8::decode(input);
let this = match discriminant { match discriminant {
0x4 => Self::V4(Decode::decode(input).unwrap()), 0x4 => {
0x6 => Self::V6(Decode::decode(input).unwrap()), let Ok(addr) = Decode::decode(input);
value => return Err(EnumDecodeError::UnassignedDiscriminant(value)) let this = Self::V4(addr);
}; Ok(this)
}
Ok(this) 0x6 => {
let Ok(addr) = Decode::decode(input);
let this = Self::V6(addr);
Ok(this)
}
value => {
__cold_path();
Err(EnumDecodeError::UnassignedDiscriminant(value))
},
}
} }
} }
@ -540,16 +558,19 @@ impl<T: Decode> Decode for Mutex<T> {
} }
impl<T: Decode> Decode for Option<T> { impl<T: Decode> Decode for Option<T> {
type Error = T::Error; type Error = EnumDecodeError<bool, <bool as Decode>::Error, T::Error>;
#[expect(clippy::if_then_some_else_none)] // ???
#[inline] #[inline]
#[track_caller] #[track_caller]
fn decode(input: &mut Input) -> Result<Self, Self::Error> { fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(sign) = bool::decode(input); let sign = bool::decode(input)
.map_err(EnumDecodeError::InvalidDiscriminant)?;
let this = if sign { let this = if sign {
Some(Decode::decode(input)?) let value = Decode::decode(input)
.map_err(EnumDecodeError::BadField)?;
Some(value)
} else { } else {
None None
}; };
@ -700,7 +721,8 @@ where
#[inline] #[inline]
#[track_caller] #[track_caller]
fn decode(input: &mut Input) -> Result<Self, Self::Error> { fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(sign) = bool::decode(input); let sign = bool::decode(input)
.map_err(EnumDecodeError::InvalidDiscriminant)?;
let this = if sign { let this = if sign {
let value = Decode::decode(input) let value = Decode::decode(input)
@ -756,8 +778,19 @@ impl Decode for SocketAddr {
let Ok(discriminant) = u8::decode(input); let Ok(discriminant) = u8::decode(input);
match discriminant { match discriminant {
0x4 => Ok(Self::V4(Decode::decode(input).unwrap())), 0x4 => {
0x6 => Ok(Self::V6(Decode::decode(input).unwrap())), let Ok(addr) = Decode::decode(input);
let this = Self::V4(addr);
Ok(this)
}
0x6 => {
let Ok(addr) = Decode::decode(input);
let this = Self::V6(addr);
Ok(this)
}
value => { value => {
__cold_path(); __cold_path();

View file

@ -12,8 +12,6 @@ use core::char;
use oct::decode::{Decode, Input}; use oct::decode::{Decode, Input};
use oct::encode::{Encode, SizedEncode}; use oct::encode::{Encode, SizedEncode};
use oct::error::EnumDecodeError; use oct::error::EnumDecodeError;
use oct::string::String;
use oct::vec::Vec;
macro_rules! test { macro_rules! test {
{ {
@ -22,7 +20,7 @@ macro_rules! test {
$($data:expr => $value:expr),+$(,)? $($data:expr => $value:expr),+$(,)?
}$(,)? }$(,)?
)* )*
} => {{ } => {
$($({ $($({
let data: &[u8] = &$data; let data: &[u8] = &$data;
@ -33,7 +31,7 @@ macro_rules! test {
::std::assert_eq!(left, right); ::std::assert_eq!(left, right);
})*)* })*)*
}}; };
} }
#[test] #[test]
@ -95,14 +93,6 @@ fn test_decode() {
[0x00, 0x00] => Ok(Ok(())), [0x00, 0x00] => Ok(Ok(())),
[0x01, 0x7F] => Ok(Err(i8::MAX)), [0x01, 0x7F] => Ok(Err(i8::MAX)),
} }
Vec<u16, 0x6> {
[0x02, 0x00, 0xBB, 0xAA, 0xDD, 0xCC] => Ok(Vec::copy_from_slice(&[0xAA_BB, 0xCC_DD]).unwrap()),
}
String<0x6> {
[0x06, 0x00, 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC] => Ok(String::new("\u{65E5}\u{672C}").unwrap()),
}
} }
} }
@ -247,14 +237,3 @@ fn test_decode_derive_custom_error() {
} }
} }
} }
#[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

@ -34,8 +34,6 @@ use std::ffi::{OsStr, OsString};
/// This trait in the form <code>DecodeBorrowed&lt;[\[T\]]&gt;</code> is not implemented for [`[T; N]`](array) due to the fact that arrays do not encode their length, instead having it hard-coded into the type, thus rendering their scheme incompatible with that of slices. /// This trait in the form <code>DecodeBorrowed&lt;[\[T\]]&gt;</code> is not implemented for [`[T; N]`](array) due to the fact that arrays do not encode their length, instead having it hard-coded into the type, thus rendering their scheme incompatible with that of slices.
/// ///
/// [\[T\]]: slice /// [\[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"))] #[doc(alias("DeserialiseBorrowed", "DeserializeBorrowed"))]
pub trait DecodeBorrowed<B: ?Sized>: Borrow<B> + Decode { } pub trait DecodeBorrowed<B: ?Sized>: Borrow<B> + Decode { }

View file

@ -8,6 +8,7 @@
mod test; mod test;
use crate::__cold_path;
use crate::encode::Output; use crate::encode::Output;
use crate::error::{ use crate::error::{
CollectionEncodeError, CollectionEncodeError,
@ -458,12 +459,12 @@ impl Encode for IpAddr {
match *self { match *self {
Self::V4(ref addr) => { Self::V4(ref addr) => {
0x4u8.encode(output).map_err(EnumEncodeError::BadDiscriminant)?; 0x4u8.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
addr.encode(output).map_err(EnumEncodeError::BadField)?; let Ok(()) = addr.encode(output);
} }
Self::V6(ref addr) => { Self::V6(ref addr) => {
0x6u8.encode(output).map_err(EnumEncodeError::BadDiscriminant)?; 0x6u8.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
addr.encode(output).map_err(EnumEncodeError::BadField)?; let Ok(()) = addr.encode(output);
} }
} }
@ -502,11 +503,17 @@ impl Encode for isize {
#[inline] #[inline]
#[track_caller] #[track_caller]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
let value = i16::try_from(*self) let value = *self;
.map_err(|_| IsizeEncodeError(*self))?;
let Ok(()) = value.encode(output); if value <= i16::MAX as Self {
Ok(()) let value = value as i16;
let Ok(()) = value.encode(output);
Ok(())
} else {
__cold_path();
Err(IsizeEncodeError(value))
}
} }
} }
@ -571,7 +578,7 @@ impl<T: Encode + ?Sized> Encode for Mutex<T> {
} }
impl<T: Encode> Encode for Option<T> { impl<T: Encode> Encode for Option<T> {
type Error = T::Error; type Error = EnumEncodeError<<bool as Encode>::Error, T::Error>;
/// Encodes a sign denoting the optional's variant. /// Encodes a sign denoting the optional's variant.
/// This is `false` for `None` instances and `true` for `Some` instances. /// This is `false` for `None` instances and `true` for `Some` instances.
@ -581,17 +588,12 @@ impl<T: Encode> Encode for Option<T> {
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
match *self { match *self {
None => { None => {
false false.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
.encode(output)
.map_err::<Self::Error, _>(|_v| unreachable!())?;
} }
Some(ref v) => { Some(ref v) => {
true true.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
.encode(output) v.encode(output).map_err(EnumEncodeError::BadField)?;
.map_err::<Self::Error, _>(|_v| unreachable!())?;
v.encode(output)?;
} }
}; };
@ -759,12 +761,8 @@ impl<T: Encode + ?Sized> Encode for RefCell<T> {
#[inline(always)] #[inline(always)]
#[track_caller] #[track_caller]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
let value = self let value = self.try_borrow().map_err(RefCellEncodeError::BadBorrow)?;
.try_borrow() T::encode(&value, output).map_err(RefCellEncodeError::BadValue)?;
.map_err(RefCellEncodeError::BadBorrow)?;
T::encode(&value, output)
.map_err(RefCellEncodeError::BadValue)?;
Ok(()) Ok(())
} }
@ -775,7 +773,7 @@ where
T: Encode<Error = Err>, T: Encode<Error = Err>,
E: Encode<Error: Into<Err>>, E: Encode<Error: Into<Err>>,
{ {
type Error = Err; type Error = EnumEncodeError<<bool as Encode>::Error, Err>;
/// Encodes a sign denoting the result's variant. /// Encodes a sign denoting the result's variant.
/// This is `false` for `Ok` instances and `true` for `Err` instances. /// This is `false` for `Ok` instances and `true` for `Err` instances.
@ -784,20 +782,18 @@ where
#[inline] #[inline]
#[track_caller] #[track_caller]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
// The sign here is `false` for `Ok` objects and
// `true` for `Err` objects.
match *self { match *self {
Ok(ref v) => { Ok(ref v) => {
let Ok(()) = false.encode(output); false.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
v.encode(output).map_err(EnumEncodeError::BadField)?;
v.encode(output)?;
} }
Err(ref e) => { Err(ref e) => {
let Ok(()) = true.encode(output); true.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
e.encode(output).map_err(Into::into)?; e.encode(output)
.map_err(Into::<Err>::into)
.map_err(EnumEncodeError::BadField)?;
} }
}; };
@ -831,7 +827,7 @@ impl<T: Encode> Encode for Saturating<T> {
} }
impl Encode for SocketAddr { impl Encode for SocketAddr {
type Error = Infallible; type Error = EnumEncodeError<<u8 as Encode>::Error, Infallible>;
/// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6). /// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6).
/// This is then followed by the respective address' own encoding (either [`SocketAddrV4`] or [`SocketAddrV6`]). /// This is then followed by the respective address' own encoding (either [`SocketAddrV4`] or [`SocketAddrV6`]).
@ -842,12 +838,12 @@ impl Encode for SocketAddr {
match *self { match *self {
Self::V4(ref addr) => { Self::V4(ref addr) => {
let Ok(()) = 0x4u8.encode(output); 0x4u8.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
let Ok(()) = addr.encode(output); let Ok(()) = addr.encode(output);
} }
Self::V6(ref addr) => { Self::V6(ref addr) => {
let Ok(()) = 0x6u8.encode(output); 0x6u8.encode(output).map_err(EnumEncodeError::BadDiscriminant)?;
let Ok(()) = addr.encode(output); let Ok(()) = addr.encode(output);
} }
} }
@ -979,11 +975,17 @@ impl Encode for usize {
#[inline] #[inline]
#[track_caller] #[track_caller]
fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { fn encode(&self, output: &mut Output) -> Result<(), Self::Error> {
let value = u16::try_from(*self) let value = *self;
.map_err(|_| UsizeEncodeError(*self))?;
if value <= u16::MAX as Self {
let value = value as u16;
let Ok(()) = value.encode(output); let Ok(()) = value.encode(output);
Ok(())
Ok(())
} else {
__cold_path();
Err(UsizeEncodeError(value))
}
} }
} }

View file

@ -8,6 +8,7 @@
mod test; mod test;
use crate::enum_encoded_size;
use crate::encode::Encode; use crate::encode::Encode;
use core::cell::{Cell, LazyCell, RefCell, UnsafeCell}; use core::cell::{Cell, LazyCell, RefCell, UnsafeCell};
@ -231,14 +232,14 @@ impl<T: SizedEncode + ?Sized> SizedEncode for RefCell<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
} }
impl<T, E, Err> SizedEncode for core::result::Result<T, E> impl<T, E, Err> SizedEncode for Result<T, E>
where where
T: SizedEncode + Encode<Error = Err>, T: Encode<Error = Err> + SizedEncode,
E: SizedEncode + Encode<Error: Into<Err>>, E: Encode<Error: Into<Err>> + SizedEncode,
{ {
const MAX_ENCODED_SIZE: usize = const MAX_ENCODED_SIZE: usize =
bool::MAX_ENCODED_SIZE bool::MAX_ENCODED_SIZE
+ if size_of::<T>() > size_of::<E>() { size_of::<T>() } else { size_of::<E>() }; + enum_encoded_size!((T), (E));
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -308,7 +309,7 @@ macro_rules! impl_tuple {
#[doc(hidden)] #[doc(hidden)]
impl<$($tys, )* E> ::oct::encode::SizedEncode for ($($tys, )*) impl<$($tys, )* E> ::oct::encode::SizedEncode for ($($tys, )*)
where where
$($tys: SizedEncode + Encode<Error = E>, )* { $($tys: Encode<Error = E> + SizedEncode, )* {
const MAX_ENCODED_SIZE: usize = 0x0 $(+ <$tys as ::oct::encode::SizedEncode>::MAX_ENCODED_SIZE)*; const MAX_ENCODED_SIZE: usize = 0x0 $(+ <$tys as ::oct::encode::SizedEncode>::MAX_ENCODED_SIZE)*;
} }
}; };

View file

@ -0,0 +1,41 @@
// 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::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
/// A boolean could not be decoded.
///
/// Boolean values are boolean.
/// This error type is emitted when <code>&lt;[bool] as [Decode]&gt;::[decode]</code> encounters a non-boolean octet.
///
/// [Decode]: [crate::decode::Decode]
/// [decode]: [crate::decode::Decode::decode]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub struct BoolDecodeError {
/// The non-boolean value.
pub value: u8,
}
impl Display for BoolDecodeError {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "value {:#02X} is not boolean", self.value)
}
}
impl Error for BoolDecodeError { }
impl From<Infallible> for BoolDecodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
unreachable!()
}
}

View file

@ -8,6 +8,8 @@
use crate::{PrimDiscriminant, PrimRepr}; use crate::{PrimDiscriminant, PrimRepr};
use crate::error::{ use crate::error::{
BoolDecodeError,
CharDecodeError,
CollectionDecodeError, CollectionDecodeError,
EnumDecodeError, EnumDecodeError,
ItemDecodeError, ItemDecodeError,
@ -31,9 +33,20 @@ use crate::error::SystemTimeDecodeError;
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum GenericDecodeError { pub enum GenericDecodeError {
/// A character was not a valid UTF-32 unit.
BadChar(CharDecodeError),
/// A string contained a non-UTF-8 sequence. /// A string contained a non-UTF-8 sequence.
BadString(Utf8Error), BadString(Utf8Error),
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
/// The [`SystemTime`](std::time::SystemTime) type was too narrow.
NarrowSystemTime(SystemTimeDecodeError),
/// A boolean was not boolean.
NonBool(BoolDecodeError),
/// A non-zero integer was null. /// A non-zero integer was null.
NullInteger(NonZeroDecodeError), NullInteger(NonZeroDecodeError),
@ -44,20 +57,26 @@ pub enum GenericDecodeError {
/// ///
/// The contained value denotes the raw, numerical value of the discriminant. /// The contained value denotes the raw, numerical value of the discriminant.
UnassignedDiscriminant(PrimDiscriminant), UnassignedDiscriminant(PrimDiscriminant),
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
/// The [`SystemTime`](std::time::SystemTime) type was too narrow.
NarrowSystemTime(SystemTimeDecodeError),
} }
impl Display for GenericDecodeError { impl Display for GenericDecodeError {
#[inline] #[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self { match *self {
Self::BadChar(ref e)
=> write!(f, "{e}"),
Self::BadString(ref e) Self::BadString(ref e)
=> write!(f, "{e}"), => write!(f, "{e}"),
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
Self::NarrowSystemTime(ref e)
=> write!(f, "{e}"),
Self::NonBool(ref e)
=> write!(f, "{e}"),
Self::NullInteger(ref e) Self::NullInteger(ref e)
=> write!(f, "{e}"), => write!(f, "{e}"),
@ -66,11 +85,6 @@ impl Display for GenericDecodeError {
Self::UnassignedDiscriminant(value) Self::UnassignedDiscriminant(value)
=> write!(f, "discriminant value `{value:#X} has not been assigned"), => write!(f, "discriminant value `{value:#X} has not been assigned"),
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
Self::NarrowSystemTime(ref e)
=> write!(f, "{e}"),
} }
} }
} }
@ -79,21 +93,39 @@ impl Error for GenericDecodeError {
#[inline] #[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self { match *self {
Self::BadChar(ref e) => Some(e),
Self::BadString(ref e) => Some(e), Self::BadString(ref e) => Some(e),
Self::NullInteger(ref e) => Some(e),
Self::SmallBuffer(ref e) => Some(e),
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))] #[cfg_attr(doc, doc(cfg(feature = "std")))]
Self::NarrowSystemTime(ref e) => Some(e), Self::NarrowSystemTime(ref e) => Some(e),
Self::NonBool(ref e) => Some(e),
Self::NullInteger(ref e) => Some(e),
Self::SmallBuffer(ref e) => Some(e),
_ => None, _ => None,
} }
} }
} }
impl From<BoolDecodeError> for GenericDecodeError {
#[inline(always)]
fn from(value: BoolDecodeError) -> Self {
Self::NonBool(value)
}
}
impl From<CharDecodeError> for GenericDecodeError {
#[inline(always)]
fn from(value: CharDecodeError) -> Self {
Self::BadChar(value)
}
}
impl<L, I> From<CollectionDecodeError<L, I>> for GenericDecodeError impl<L, I> From<CollectionDecodeError<L, I>> for GenericDecodeError
where where
L: Into<Self>, L: Into<Self>,

View file

@ -12,12 +12,8 @@ use core::fmt::{self, Display, Formatter};
/// A collection buffer was too small to contain all of its elements. /// A collection buffer was too small to contain all of its elements.
/// ///
/// Some data types use a statically-sized buffer whilst still allowing for partial usage of this buffer (e.g. [`Vec`](crate::vec::Vec)). /// Some data types use a statically-sized buffer whilst still allowing for partial usage of this buffer.
/// These types should return this error in cases where their size limit has exceeded. /// These types should return this error in cases where their size limit has exceeded.
///
/// Taking `Vec` as an example, it encodes its actual length before encoding its elements.
/// It is allowed for any smaller-sized `Vec` instance to decode a larger-sized encoding **if** the actual length is still within bounds.
/// Otherwise, this error type is used to denote the error state.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
#[must_use] #[must_use]
pub struct LengthError { pub struct LengthError {

View file

@ -11,6 +11,7 @@
//! This module defines the error types used by Oct. //! This module defines the error types used by Oct.
//! All of these types define (at least conditionally) the [`Error`](core::error::Error) trait. //! All of these types define (at least conditionally) the [`Error`](core::error::Error) trait.
mod bool_decode_error;
mod char_decode_error; mod char_decode_error;
mod collection_decode_error; mod collection_decode_error;
mod collection_encode_error; mod collection_encode_error;
@ -30,6 +31,7 @@ mod system_time_decode_error;
mod usize_encode_error; mod usize_encode_error;
mod utf8_error; mod utf8_error;
pub use bool_decode_error::BoolDecodeError;
pub use char_decode_error::CharDecodeError; pub use char_decode_error::CharDecodeError;
pub use collection_decode_error::CollectionDecodeError; pub use collection_decode_error::CollectionDecodeError;
pub use collection_encode_error::CollectionEncodeError; pub use collection_encode_error::CollectionEncodeError;

View file

@ -209,7 +209,7 @@
//! Oct does not accept source code contributions at the moment. //! Oct does not accept source code contributions at the moment.
//! This is a personal choice by the maintainer and may be undone in the future. //! This is a personal choice by the maintainer and may be undone in the future.
//! //!
//! Do however feel free to open an issue on [GitLab](https://gitlab.com/bjoernager/oct/issues/) or [GitHub](https://github.com/bjoernager/oct/issues/) if you feel the need to express any concerns over the project. //! Do however feel free to open an issue on [GitLab](https://gitlab.com/bjoernager/oct/issues/), on [GitHub](https://github.com/bjoernager/oct/issues/), or on [`mandelbrot.dk`](https://mandelbrot.dk/bjoernager/oct/issues/) (if a member) if you feel the need to express any concerns over the project.
//! //!
//! # Copyright & Licence //! # Copyright & Licence
//! //!
@ -245,8 +245,6 @@ mod prim_repr;
pub mod decode; pub mod decode;
pub mod encode; pub mod encode;
pub mod error; pub mod error;
pub mod string;
pub mod vec;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub mod slot; pub mod slot;
@ -256,45 +254,32 @@ pub use prim_repr::PrimRepr;
pub(crate) use prim_repr::SealedPrimRepr; pub(crate) use prim_repr::SealedPrimRepr;
/// Directly constructs a [`Vec`](crate::vec::Vec) object. #[doc(hidden)]
///
/// This macro tests at compile-time whether the provided data can fit into the inferred length.
/// Compilation will fail if this is not the case.
#[macro_export] #[macro_export]
macro_rules! vec { macro_rules! enum_encoded_size {
[$value:literal; $len:expr] => {{ ($(($($tys:ty),+$(,)?)),*$(,)?) => {
const { $crate::vec::Vec::new([$value; $len]) } const {
}}; #[allow(unused)]
use ::oct::encode::SizedEncode;
[$($value:expr),+ $(,)?] => {{ let mut total_size = 0x0usize;
const { $crate::vec::Vec::new([$($value,)*]) }
}};
[] => {{ $({
const { $crate::vec::Vec::new([]) } let current_size = 0x0usize $(+ <$tys as SizedEncode>::MAX_ENCODED_SIZE)*;
}}
}
if current_size > total_size {
total_size = current_size;
}
})*
/// Directly constructs a [`String`](crate::string::String) object. total_size
/// }
/// This macro tests at compile-time whether the string literal can fit into the inferred length. };
/// Compilation will fail if this is not the case.
#[macro_export]
macro_rules! string {
($s:expr) => {{
const { $crate::string::__string($s) }
}};
() => {{
const { $crate::string::__string("") }
}};
} }
// NOTE: Stable equivalent of `core::hint:: // NOTE: Stable equivalent of `core::hint::
// cold_path`. Should not be used directly, but is // cold_path`. Should not be used directly, but is
// used in derive macros. // used in derive macros.
#[doc(hidden)] #[doc(hidden)]
#[inline(always)]
#[cold] #[cold]
pub const fn __cold_path() { } pub const fn __cold_path() { }

View file

@ -32,23 +32,25 @@ use core::slice::{self, SliceIndex};
/// Create a slot for holding a `Request` enumeration: /// Create a slot for holding a `Request` enumeration:
/// ///
/// ```rust /// ```rust
/// use oct::string;
/// use oct::encode::{Encode, Output, SizedEncode}; /// use oct::encode::{Encode, Output, SizedEncode};
/// use oct::slot::Slot; /// use oct::slot::Slot;
/// use oct::string::String; /// use std::string::String;
/// ///
/// #[non_exhaustive]
/// #[derive(Debug, Encode)] /// #[derive(Debug, Encode)]
/// enum Request { /// enum Request {
/// Join { username: String<0x40> }, /// Join { username: String },
/// ///
/// Quit { username: String<0x40> }, /// Quit { username: String },
/// ///
/// SendMessage { message: String<0x80> }, /// SendMessage { message: String },
///
/// // ...
/// } /// }
/// ///
/// let mut buf = Slot::with_capacity(0x100); /// let mut buf = Slot::with_capacity(0x100);
/// ///
/// buf.write(&Request::Join { username: string!("epsiloneridani") }).unwrap(); /// buf.write(&Request::Join { username: "epsiloneridani".into() }).unwrap();
/// assert_eq!(buf.as_slice(), b"\0\0\x0E\0epsiloneridani"); /// assert_eq!(buf.as_slice(), b"\0\0\x0E\0epsiloneridani");
/// ///
/// // ... /// // ...

View file

@ -1,13 +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/>.
//! String container.
mod string;
pub use string::{__string, String};

View file

@ -1,738 +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 test;
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::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};
#[cfg(feature = "alloc")]
use {
alloc::borrow::Cow,
alloc::boxed::Box,
};
#[cfg(feature = "std")]
use {
std::ffi::OsStr,
std::net::ToSocketAddrs,
std::path::Path,
};
/// String container with maximum length.
///
/// 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 the standard library's [`Vec`](alloc::vec::Vec).
///
/// # Examples
///
/// All instances of this type have the same size if the value of `N` is also the same.
/// Therefore, the following four strings have -- despite their different contents -- the same total size.
///
/// ```rust
/// use oct::string::String;
/// use std::str::FromStr;
///
/// 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(&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, Copy)]
pub struct String<const N: usize> {
len: usize,
buf: [u8; N],
}
impl<const N: usize> String<N> {
/// Constructs a new, size-constrained string.
///
/// 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]
#[track_caller]
pub const fn new(s: &str) -> Result<Self, LengthError> {
let len = s.len();
if len > N {
return Err(LengthError {
remaining: N,
count: len,
});
}
let mut buf = [0x00; N];
unsafe {
let src = s.as_ptr();
let dst = buf.as_mut_ptr();
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)
}
/// Unsafely constructs a new, size-constrained string.
///
/// See also [`new`](Self::new) for a safe alternative to this constructor.
///
/// # Safety
///
/// If the internal buffer cannot contain the entirety of `s`, then the call to this constructor will result in undefined behaviour.
#[inline]
#[track_caller]
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();
copy_nonoverlapping(src, dst, len);
}
// SAFETY: `s` 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]
#[track_caller]
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]
#[track_caller]
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() as *const [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]
#[track_caller]
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.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.len() == 0x0
}
/// 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_char_boundary(&self, index: usize) -> bool {
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 as *const [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()
}
}
impl<const N: usize> Debug for String<N> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self.as_str(), f)
}
}
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]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self.as_str(), f)
}
}
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 {
let mut buf = [0x00; N];
let mut len = 0x0;
for c in iter {
let rem = N - len;
let req = c.len_utf8();
if rem < req { break }
let start = len;
let end = start + req;
c.encode_utf8(&mut buf[start..end]);
len += req;
}
// SAFETY: All octets are initialised and come from
// `char::encode_utf8`.
unsafe { Self::from_raw_parts(buf, len) }
}
}
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) {
self.as_str().hash(state)
}
}
impl<I: SliceIndex<str>, const N: usize> Index<I> for String<N> {
type Output = I::Output;
#[inline(always)]
#[track_caller]
fn index(&self, index: I) -> &Self::Output {
self.get(index).unwrap()
}
}
impl<I: SliceIndex<str>, const N: usize> IndexMut<I> for String<N> {
#[inline(always)]
#[track_caller]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.get_mut(index).unwrap()
}
}
impl<const N: usize> Ord for String<N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
(**self).cmp(&**other)
}
}
impl<const N: usize, const M: usize> PartialEq<String<M>> for String<N> {
#[inline(always)]
fn eq(&self, other: &String<M>) -> bool {
**self == **other
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &String<M>) -> bool {
**self != **other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialEq<Cow<'_, str>> for String<N> {
#[inline(always)]
fn eq(&self, other: &Cow<str>) -> bool {
**self == **other
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &Cow<str>) -> bool {
**self != **other
}
}
impl<const N: usize> PartialEq<str> for String<N> {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
**self == *other
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &str) -> bool {
**self == *other
}
}
impl<const N: usize> PartialEq<&str> for String<N> {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
**self == **other
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&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 == **other
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &alloc::string::String) -> bool {
*self != **other
}
}
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).partial_cmp(other)
}
#[inline(always)]
fn lt(&self, other: &String<M>) -> bool {
**self < **other
}
#[inline(always)]
fn le(&self, other: &String<M>) -> bool {
**self <= **other
}
#[inline(always)]
fn gt(&self, other: &String<M>) -> bool {
**self > **other
}
#[inline(always)]
fn ge(&self, other: &String<M>) -> bool {
**self >= **other
}
}
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()
}
}
// NOTE: This function is used by the `str` macro
// to circumvent itself using code which may be
// forbidden by the macro user's lints. While this
// function is sound, please do not call it direct-
// ly. It is not a breaking change if it is re-
// moved.
#[doc(hidden)]
#[inline(always)]
#[must_use]
#[track_caller]
pub const fn __string<const N: usize>(s: &'static str) -> String<N> {
assert!(s.len() <= N, "cannot construct string from literal that is longer");
// SAFETY: `s` has been tested to not contain more
// than `N` octets.
unsafe { String::new_unchecked(s) }
}

View file

@ -1,94 +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/>.
#![cfg(test)]
use core::cmp::Ordering;
use oct::string;
use oct::error::Utf8Error;
use oct::string::String;
use oct::vec::Vec;
#[test]
fn test_str_macro() {
let s0: String<0x3> = string!("Oct");
let s1: String<0x3> = string!("Oct");
assert_eq!(s0, s1);
}
#[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> = string!("Hello there!");
let s1: String<0x12> = string!("MEIN_GRO\u{1E9E}_GOTT");
let s2: String<0x05> = string!("Hello");
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::copy_from_slice(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,373 +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 test;
use crate::vec::{clone_to_uninit_in_range, Vec};
use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator};
use core::mem::MaybeUninit;
use core::ptr::drop_in_place;
use core::slice;
/// Owning iterator to a vector.
///
/// This type is exclusively used by the deconstruction of the [`Vec`] type, as per <code>[IntoIterator]::[into_iter](IntoIterator::into_iter)</code>.
#[must_use]
pub struct IntoIter<T, const N: usize> {
/// The cursor position in the buffer.
///
/// # Safety
///
/// This field may not be greater than [`isize::MAX`].
pos: usize,
/// The length; the count remaining of alive elements remaining in the buffer.
///
/// # Safety
///
/// This field may not be greater than [`isize::MAX`].
len: usize,
/// The internal buffer.
///
/// # Safety
///
/// We must **always** guarantee that all objects in the range `pos..pos + len` are initialised and alive.
/// One may therefore assume that interpreting these objects as such is valid.
buf: [MaybeUninit<T>; N],
}
impl<T, const N: usize> IntoIter<T, N> {
/// Constructs a new, owning iterator to a vector.
#[inline(always)]
#[track_caller]
pub(super) fn new(v: Vec<T, N>) -> Self {
let (buf, len) = v.into_raw_parts();
let pos = Default::default();
Self { pos, len, buf }
}
/// Incremenets the cursor position by a specified amount.
///
/// The caller is responsible for dropping the skipped elements.
///
/// # Safety
///
/// The iterator `self` may not contain less than `count` elements.
#[inline(always)]
#[track_caller]
unsafe fn advance_by_unchecked(&mut self, count: usize) {
debug_assert!(count <= self.len);
// SAFETY: The caller guarantees that at least
// `count` element are remaining.
self.len = unsafe { self.len.unchecked_sub(count) };
// SAFETY: It is not invalid for us to go one-past-
// the-end or exceed `isize::MAX`; `len` guarantees
// that this counter will not be used as a pointer
// offset in such cases.
self.pos = unsafe { self.pos.unchecked_add(count) };
}
/// Decrements the length counter by a specified amount.
///
/// The caller is responsible for dropping the skipped elements.
///
/// # Safety
///
/// The iterator `self` may not contain less than `count` elements.
#[inline(always)]
#[track_caller]
unsafe fn advance_back_by_unchecked(&mut self, count: usize) {
debug_assert!(count <= self.len);
// SAFETY: The caller guarantees that at least
// `count` element are remaining.
self.len = unsafe { self.len.unchecked_sub(count) };
}
/// Gets a pointer to the current element.
///
/// If the iterator `self` is currently empty, then the returned pointer will instead be dangling.
#[inline(always)]
fn as_ptr(&self) -> *const T {
let pos = self.pos;
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let base = self.buf.as_ptr() as *const T;
unsafe { base.add(pos) }
}
/// Gets a mutable pointer to the current element.
///
/// If the iterator `self` is currently empty, then the returned pointer will instead be dangling.
#[inline(always)]
fn as_mut_ptr(&mut self) -> *mut T {
let pos = self.pos;
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let base = self.buf.as_mut_ptr() as *mut T;
unsafe { base.add(pos) }
}
/// Gets a slice of the remaining elements.
#[inline(always)]
pub fn as_slice(&self) -> &[T] {
let len = self.len;
let ptr = self.as_ptr();
unsafe { slice::from_raw_parts(ptr, len) }
}
/// Gets a mutable slice of the remaining elements.
#[inline(always)]
pub fn as_mut_slice(&mut self) -> &mut [T] {
let len = self.len;
let ptr = self.as_mut_ptr();
unsafe { slice::from_raw_parts_mut(ptr, len) }
}
}
impl<T, const N: usize> AsMut<[T]> for IntoIter<T, N> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T, const N: usize> AsRef<[T]> for IntoIter<T, N> {
#[inline(always)]
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}
impl<T: Clone, const N: usize> Clone for IntoIter<T, N> {
#[inline]
fn clone(&self) -> Self {
let pos = self.pos;
let len = self.len;
let mut buf = [const { MaybeUninit::uninit() }; N];
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let src = self.buf.as_ptr() as *const T;
let dst = buf.as_mut_ptr() as *mut T;
let start = pos;
let end = pos + len;
// SAFETY: The range
//
// pos..pos + len
//
// defines in and of itself the bounds of valid
// elements.
unsafe { clone_to_uninit_in_range(src, dst, start..end) };
// SAFETY: The buffer has been initialised in the
// provided range - which does not extend beyond
// bounds.
Self { pos, len, buf }
}
}
impl<T, const N: usize> Default for IntoIter<T, N> {
#[inline(always)]
fn default() -> Self {
Vec::default().into_iter()
}
}
impl<T, const N: usize> DoubleEndedIterator for IntoIter<T, N> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
// Test whether the iterator is empty.
if self.len == 0x0 {
return None;
}
// Take the next value.
// Get a pointer to the next item.
// SAFETY: `self.pos` is guaranteed to always be
// within bounds. `self.pos + self.len` is guaran-
// teed one-past-the-end index.
let index = self.pos + self.len - 0x1;
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let base = self.buf.as_ptr() as *const T;
let item = unsafe { base.add(index) };
// Read the item value.
let value = unsafe { item.read() };
// Update counters, **not** including the position.
// SAFETY: We have tested that at least one element
// remains.
unsafe { self.advance_back_by_unchecked(0x1) };
Some(value)
}
}
impl<T, const N: usize> Drop for IntoIter<T, N> {
#[inline(always)]
fn drop(&mut self) {
// Drop every element that hasn't been consumed.
let remaining = self.as_mut_slice();
unsafe { drop_in_place(remaining) };
// We do not need to ensure that `self` is in a
// valid state after this call to `drop`.
}
}
impl<T, const N: usize> ExactSizeIterator for IntoIter<T, N> {
#[inline(always)]
fn len(&self) -> usize {
self.len
}
}
impl<T, const N: usize> FusedIterator for IntoIter<T, N> { }
impl<T, const N: usize> Iterator for IntoIter<T, N> {
type Item = T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// Test whether the iterator is empty.
if self.len == 0x0 {
return None;
}
// Take the next value.
// Get a pointer to the next item.
// SAFETY: `self.pos` is guaranteed to always be
// within bounds.
let index = self.pos;
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let base = self.buf.as_ptr() as *const T;
let item = unsafe { base.add(index) };
// Read the item value.
// SAFETY: We guarantee that all items in the range
//
// self.pos..self.pos + self.len
//
// are alive (and initialised).
let value = unsafe { item.read() };
// Update counters.
// SAFETY: We have tested that at least one element
// remains.
unsafe { self.advance_by_unchecked(0x1) };
// Return the item value.
Some(value)
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
// Test whether the iterator is empty.
if n >= self.len {
return None;
}
// Get the indices of the involved items.
let drop_start = self.pos;
let drop_end = drop_start + n;
let index = drop_end;
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let base = self.buf.as_mut_ptr() as *mut T;
// Drop each skipped element.
for i in drop_start..drop_end {
let item = unsafe { base.add(i) };
// SAFETY: We guarantee that all items in the range
//
// self.pos..self.pos + self.len
//
// are alive (and initialised).
unsafe { drop_in_place(item) };
}
// Read the final value.
let item = unsafe { base.add(index) };
let value = unsafe { item.read() };
// Update counters.
// SAFETY: This cannot overflow as `n` has been
// tested to be less than `self.len`, which itself
// cannot be greater than `isize::MAX`.
let count = unsafe { n.unchecked_add(0x1) };
// SAFETY: We have tested that there are at least
// `count` elements left.
unsafe { self.advance_by_unchecked(count) };
// Return the value.
Some(value)
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len;
(len, Some(len))
}
#[inline(always)]
fn count(self) -> usize {
// NOTE: Elements are dropped automatically.
self.len
}
#[inline(always)]
fn is_sorted(self) -> bool
where
T: PartialOrd,
{
self.as_slice().is_sorted()
}
#[inline(always)]
fn is_sorted_by<F: FnMut(&Self::Item, &Self::Item) -> bool>(self, compare: F) -> bool {
self.as_slice().is_sorted_by(compare)
}
}

View file

@ -1,86 +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/>.
#![cfg(test)]
use oct::string;
use oct::string::String;
use oct::vec::Vec;
#[test]
fn test_vec_iter_clone() {
let data: String::<0xF> = string!("fran\u{00E7}aise");
assert_eq!(data.len(), 0xA);
assert_eq!(data.as_bytes(), b"fran\xC3\xA7aise");
let mut iter0 = data.into_bytes().into_iter();
assert_eq!(iter0.len(), 0xA);
assert_eq!(iter0.as_slice(), b"fran\xC3\xA7aise");
assert_eq!(iter0.nth(0x3), Some(b'n'));
assert_eq!(iter0.len(), 0x6);
assert_eq!(iter0.as_slice(), b"\xC3\xA7aise");
let mut iter1 = iter0.clone();
assert_eq!(iter1.len(), 0x6);
assert_eq!(iter1.as_slice(), b"\xC3\xA7aise");
assert_eq!(iter0.next(), Some(0xC3));
assert_eq!(iter1.next(), Some(0xC3));
assert_eq!(iter0.next(), Some(0xA7));
assert_eq!(iter1.next(), Some(0xA7));
assert_eq!(iter0.next(), Some(b'a'));
assert_eq!(iter1.next(), Some(b'a'));
assert_eq!(iter0.next(), Some(b'i'));
assert_eq!(iter1.next(), Some(b'i'));
assert_eq!(iter0.next(), Some(b's'));
assert_eq!(iter1.next(), Some(b's'));
assert_eq!(iter0.next(), Some(b'e'));
assert_eq!(iter1.next(), Some(b'e'));
assert_eq!(iter0.next(), None);
assert_eq!(iter1.next(), None);
}
#[test]
fn test_vec_iter_double_ended() {
let data = Vec::from([
'H', 'E', 'L', 'L', 'O', ' ', 'W', 'O',
'R', 'L', 'D',
]);
let mut data = data.into_iter();
assert_eq!(data.next(), Some('H'));
assert_eq!(data.next_back(), Some('D'));
assert_eq!(data.next(), Some('E'));
assert_eq!(data.next_back(), Some('L'));
assert_eq!(data.next(), Some('L'));
assert_eq!(data.next_back(), Some('R'));
assert_eq!(data.next(), Some('L'));
assert_eq!(data.next_back(), Some('O'));
assert_eq!(data.next(), Some('O'));
assert_eq!(data.next_back(), Some('W'));
assert_eq!(data.next(), Some(' '));
}
#[test]
fn test_vec_new() {
assert_eq!(
Vec::<u32, 0x6>::new([0x6]),
[0x6].as_slice(),
);
assert_eq!(
Vec::<u32, 0x6>::new([0x6; 0x6]),
[0x6, 0x6, 0x6, 0x6, 0x6, 0x6].as_slice(),
);
}

View file

@ -1,40 +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/>.
//! Vector container and iterators.
mod into_iter;
mod vec;
pub use into_iter::IntoIter;
pub use vec::Vec;
use core::ops::Range;
#[inline]
unsafe fn clone_to_uninit_in_range<T: Clone>(src: *const T, dst: *mut T, range: Range<usize>) {
// SAFETY: The caller guarantees a valid range.
for i in range.start..range.end {
// SAFETY: We guarantee that all items in the range
//
// 0x0..self.len
//
// are alive (and initialised).
let src_item = unsafe { &*src.add(i) };
let dst_item = unsafe { dst.add(i) };
// Clone the item value.
let value = src_item.clone();
// Write the item value.
unsafe { dst_item.write(value) };
}
}

View file

@ -1,773 +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 test;
use crate::decode::{self, Decode, DecodeBorrowed};
use crate::encode::{self, Encode, SizedEncode};
use crate::error::{CollectionDecodeError, ItemDecodeError, LengthError};
use crate::vec::{clone_to_uninit_in_range, IntoIter};
use core::borrow::{Borrow, BorrowMut};
use core::cmp::Ordering;
use core::fmt::{self, Debug, Formatter};
use core::hash::{Hash, Hasher};
use core::mem::{offset_of, ManuallyDrop, MaybeUninit};
use core::ops::{Deref, DerefMut, Index, IndexMut};
use core::ptr::{copy_nonoverlapping, drop_in_place};
use core::slice::{self, SliceIndex};
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
/// Vector container with maximum length.
///
/// This type is intended as a [sized-encodable](crate::encode::SizedEncode) and [decodable](crate::decode::Decode) alternative to slices and the standard library's [`Vec`](alloc::vec::Vec) (in cases where [arrays](array) may not be wanted).
///
/// Note that this type is immutable in the sense that it does **not** define methods like `push` and `pop`, in contrast to the standard library type.
///
/// See [`String`](crate::string::String) for an equivalent alternative to the standard library's [`String`](alloc::string::String).
///
/// # Examples
///
/// All instances of this type with the same `T` and `N` also have the exact same layout:
///
/// ```rust
/// use oct::vec::Vec;
///
/// 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(&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> {
len: usize,
buf: [MaybeUninit<T>; N],
}
impl<T, const N: usize> Vec<T, N> {
/// Constructs a new slice from existing data.
///
/// This constructor takes inherits an existing array and converts it to a vector.
/// If the provided array's length `M` is greater than `N`, then this function will panic at compile-time.
#[inline(always)]
#[must_use]
#[track_caller]
pub const fn new<const M: usize>(data: [T; M]) -> Self {
const { assert!(M <= N, "cannot construct vector from array that is longer") };
let data = ManuallyDrop::new(data);
let len = M;
let buf = if N == M {
// Reuse the existing buffer.
// SAFETY: `ManuallyDrop<[T; N]>` and
// `[MaybeUninit<T>; N]` are both transparent to
// `[T; N]`. `data` can also be forgotten as its
// constructor is supressed. Also remember that
// `N` and `M` have been tested to be equal.
let ptr = &raw const data as *const [MaybeUninit<T>; N];
unsafe { ptr.read() }
} else {
// Reallocate the buffer to `N` elements.
// SAFETY: `ManuallyDrop<T>` is transparent to `T`.
let data = unsafe { &*(&raw const data as *const [T; M]) };
let mut buf = [const { MaybeUninit::uninit() }; N];
let src = data.as_ptr();
let dst = buf.as_mut_ptr() as *mut T;
unsafe { copy_nonoverlapping(src, dst, len) };
buf
};
// SAFETY: We have checked that the length is with-
// in bounds.
unsafe { Self::from_raw_parts(buf, len) }
}
/// Copies elements from a slice into a new vector.
///
/// This constructor copies the raw representation of `data` into a new allocation of `N` elements.
///
/// # Errors
///
/// If `self` cannot contain the entirety of `data`, then this method will return an error.
#[inline]
#[track_caller]
pub const fn copy_from_slice(data: &[T]) -> Result<Self, LengthError>
where
T: Copy,
{
let len = data.len();
if len > N {
return Err(LengthError {
remaining: N,
count: len,
});
}
// SAFETY: We have checked that the length is with-
// in bounds.
let this = unsafe { Self::copy_from_slice_unchecked(data) };
Ok(this)
}
/// Constructs a new slice from existing data without checking bounds.
///
/// For a safe version of this constructor, see [`copy_from_slice`](Self::copy_from_slice).
///
/// # Safety
///
/// The entirety of `data` must be able to fit into an array of `N` elements.
#[inline(always)]
#[track_caller]
pub const unsafe fn copy_from_slice_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() as *mut T;
// SAFETY: `T` implements `Copy`.
copy_nonoverlapping(src, dst, len);
}
// SAFETY: The relevant elements have been ini-
// tialised, and `len` is not greater than `N` - as
// guaranteed by the caller.
unsafe { Self::from_raw_parts(buf, len) }
}
/// 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]
#[track_caller]
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 }
}
/// Generates a new vector referencing the elements of `self`.
///
/// # Safety
///
/// `len` may not be greater than `N`.
#[inline]
#[must_use]
const unsafe fn each_ptr(data: *mut [MaybeUninit<T>; N], len: usize) -> [MaybeUninit<*mut T>; N] {
// SAFETY: Note that this function does not take
// any `&_` reference, so the caller can safely
// reinterpret the returned array as `&mut _` muta-
// ble references.
debug_assert!(len <= N);
let mut buf = [const { MaybeUninit::uninit() }; N];
let src_base = data as *mut T;
let dst_base = buf.as_mut_ptr();
let mut i = 0x0;
while i < len {
// SAFETY: `i` will always be less than `self.len`
// and thereby within bounds as that variable is
// also always less than `N`.
let slot = unsafe { &mut *dst_base.add(i) };
let value = unsafe { src_base.add(i) };
slot.write(value);
i += 0x1;
}
buf
}
/// Generates a new vector referencing the elements of `self`.
#[inline(always)]
#[must_use]
pub const fn each_ref(&self) -> Vec<&T, N> {
let buf = (&raw const self.buf).cast_mut();
let len = self.len;
let buf = unsafe { Self::each_ptr(buf, len) };
// SAFETY: every relavent pointer (i.e. the first
// `len`) have been initialised as valid refer-
// ences. The destructor of the original `buf` does
// also not show any substantial effects.
let buf = unsafe { (&raw const buf as *const [MaybeUninit<&T>; N]).read() };
unsafe { Vec::from_raw_parts(buf, len) }
}
/// Generates a new vector mutably-referencing the elements of `self`.
#[inline(always)]
#[must_use]
pub const fn each_mut(&mut self) -> Vec<&mut T, N> {
let len = self.len;
let buf = unsafe { Self::each_ptr(&raw mut self.buf, len) };
// SAFETY: every relavent pointer (i.e. the first
// `len`) have been initialised as valid refer-
// ences. The destructor of the original `buf` does
// also not show any substantial effects.
let buf = unsafe { (&raw const buf as *const [MaybeUninit<&mut T>; N]).read() };
unsafe { Vec::from_raw_parts(buf, len) }
}
/// Unsafely sets the length of the vector.
///
/// The provided length is **not** tested in any way.
///
/// # Safety
///
/// The new length `len` may not be larger than `N`.
///
/// It is only valid to enlarge vectors if `T` supports being in a purely uninitialised state
/// Such is permitted by e.g. [`MaybeUninit`].
#[inline(always)]
#[track_caller]
pub const unsafe fn set_len(&mut self, len: usize) {
debug_assert!(len <= N, "cannot set length past bounds");
// SAFETY: The caller guarantees that `len` is not
// freaky.
self.len = len
}
/// Returns the length of the vector.
///
/// 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 {
self.len
}
/// Checks if the vector is empty, i.e. no elements are recorded.
///
/// Note that the internal buffer may still contain objects that have been "shadowed" by setting a smaller length with [`len`](Self::len).
#[inline(always)]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len() == 0x0
}
/// 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 {
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
self.buf.as_ptr() as *const T
}
/// 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 {
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
self.buf.as_mut_ptr() as *mut T
}
/// 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 len = self.len();
let ptr = self.as_ptr();
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 len = self.len();
let ptr = self.as_mut_ptr();
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 len = self.len;
let this = ManuallyDrop::new(self);
// FIXME(const-hack): We can't just use drop glue.
let buf = {
// SAFETY: `ManuallyDrop<T>` is transparent to `T`.
let base = &raw const this as *const Self;
let off = offset_of!(Self, buf);
let ptr = unsafe { base.byte_add(off) as *const [MaybeUninit<T>; N] };
// SAFETY: `this` will not be dropped with its own
// destructor, so we can safely move out of it.
unsafe { ptr.read() }
};
(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")))]
#[inline]
#[must_use]
#[track_caller]
pub fn into_boxed_slice(self) -> Box<[T]> {
let (buf, len) = self.into_raw_parts();
let mut boxed = alloc::vec::Vec::with_capacity(len).into_boxed_slice();
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let src = buf.as_ptr() as *const T;
let dst = boxed.as_mut_ptr();
// SAFETY: `boxed` has been allocated with at least
// `len` elements.
unsafe { copy_nonoverlapping(src, dst, len) };
boxed
}
/// 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]
#[track_caller]
pub fn into_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
}
}
impl<T, const N: usize> AsRef<[T]> for Vec<T, N> {
#[inline(always)]
fn as_ref(&self) -> &[T] {
self
}
}
impl<T, const N: usize> Borrow<[T]> for Vec<T, N> {
#[inline(always)]
fn borrow(&self) -> &[T] {
self
}
}
impl<T, const N: usize> BorrowMut<[T]> for Vec<T, N> {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut [T] {
self
}
}
impl<T: Clone, const N: usize> Clone for Vec<T, N> {
#[inline]
fn clone(&self) -> Self {
let len = self.len;
let mut buf = [const { MaybeUninit::uninit() }; N];
// SAFETY: `MaybeUninit<T>` is transparent to `T`.
let src = self.buf.as_ptr() as *const T;
let dst = buf.as_mut_ptr() as *mut T;
// SAFETY: The range
//
// 0x0..len
//
// defines in and of itself the bounds of valid
// elements.
unsafe { clone_to_uninit_in_range(src, dst, 0x0..len) };
// SAFETY: The buffer has been initialised in the
// provided range - which does not extend beyond
// bounds.
unsafe { Self::from_raw_parts(buf, len) }
}
}
impl<T: Debug, const N: usize> Debug for Vec<T, N> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self.as_slice(), f)
}
}
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 Ok(len) = Decode::decode(input);
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 {
let buf = [const { MaybeUninit::uninit() }; N];
// SAFETY: The resulting vector is zero lengthed
// and does therefore not expose any uninitialised
// objects.
unsafe { Self::from_raw_parts(buf, Default::default()) }
}
}
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) {
// Drop every element that is currently alive.
let remaining = self.as_mut_slice() as *mut [T];
// SAFETY: Mutable references always point to alive
// and initialised objects.
unsafe { drop_in_place(remaining) };
// We do not need to ensure that `self` is in a
// valid state after this call to `drop`.
}
}
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).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 {
Self::new(value)
}
}
impl<T, const N: usize> FromIterator<T> for Vec<T, N> {
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut len = 0x0;
let mut buf = [const { MaybeUninit::<T>::uninit() }; N];
for (slot, value) in buf.iter_mut().zip(iter) {
slot.write(value);
len += 0x1;
}
// Drop the remaining elements.
Self { len, buf }
}
}
impl<T: Hash, const N: usize> Hash for Vec<T, N> {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
(**self).hash(state)
}
}
impl<T, I: SliceIndex<[T]>, const N: usize> Index<I> for Vec<T, N> {
type Output = I::Output;
#[inline(always)]
#[track_caller]
fn index(&self, index: I) -> &Self::Output {
Index::index(&**self, index)
}
}
impl<T, I: SliceIndex<[T]>, const N: usize> IndexMut<I> for Vec<T, N> {
#[inline(always)]
#[track_caller]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
IndexMut::index_mut(&mut **self, index)
}
}
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 {
IntoIter::new(self)
}
}
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).cmp(&**other)
}
}
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 == **other
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &Vec<U, M>) -> bool {
**self != **other
}
}
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
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &[U; M]) -> bool {
**self != *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
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &[U]) -> bool {
**self != *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
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&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 == **other
}
#[expect(clippy::partialeq_ne_impl)]
#[inline(always)]
fn ne(&self, other: &alloc::vec::Vec<U>) -> bool {
**self != **other
}
}
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).partial_cmp(&**other)
}
#[inline(always)]
fn lt(&self, other: &Vec<T, M>) -> bool {
**self < **other
}
#[inline(always)]
fn le(&self, other: &Vec<T, M>) -> bool {
**self <= **other
}
#[inline(always)]
fn gt(&self, other: &Vec<T, M>) -> bool {
**self > **other
}
#[inline(always)]
fn ge(&self, other: &Vec<T, M>) -> bool {
**self >= **other
}
}
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::copy_from_slice(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_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 == **other
}
}

View file

@ -1,95 +0,0 @@
// Copyright 2024-str2025 Gabriel Bjørnager Jensen.
//
// This file is part of Oct.
//
// 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/>.
#![cfg(test)]
use oct::vec;
use oct::vec::Vec;
#[test]
fn test_vec_copy_from_slice() {
const DATA: &[i64] = &[
-0x67,
-0x51,
0x07,
0x0D,
0x14,
0x1A,
0x1F,
0x24,
0x2A,
0x2F,
0x37,
];
assert_eq!(
Vec::<_, 0xB0>::copy_from_slice(DATA).as_deref(),
Ok([-0x67, -0x51, 0x7, 0xD, 0x14, 0x1A, 0x1F, 0x24, 0x2A, 0x2F, 0x37].as_slice()),
);
}
#[test]
fn test_vec_from_array() {
const DATA: [i64; 0x6] = [
-0x1E,
0x0D,
0x0F,
0x12,
0x18,
0x1C,
];
assert_eq!(
Vec::<_, 0x6>::new(DATA),
[-0x1E, 0xD, 0xF, 0x12, 0x18, 0x1C],
);
assert_eq!(
Vec::<_, 0x60>::new(DATA),
[-0x1E, 0xD, 0xF, 0x12, 0x18, 0x1C],
);
assert_eq!(
Vec::<_, 0x6>::from(DATA),
[-0x1E, 0xD, 0xF, 0x12, 0x18, 0x1C],
);
}
#[test]
fn test_vec_from_iter() {
let f = |x: u32| -> u32 {
let x = f64::from(x);
let y = x.sin().powi(0x2) * 1000.0;
y as u32
};
let mut v = alloc::vec::Vec::new();
for x in 0x0..0x8 {
v.push(f(x));
}
let v: Vec<_, 0x10> = v.into_iter().collect();
assert_eq!(
v,
[0, 708, 826, 19, 572, 919, 78, 431],
);
}
#[test]
fn test_vec_macro() {
let v0: Vec<u8, 0x4> = vec![0xEF; 0x3];
let v1: Vec<u8, 0x4> = vec![0xEF, 0xEF, 0xEF];
assert_eq!(v0, v1);
}