diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af29d7..c6bf81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` 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` 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 `::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` for `String` +* Unimplement `PartialOrd<{[T; M], &[T], alloc::vec::Vec}>` for `Vec` +* 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` +* Implement `PartialEq>` for `alloc::vec::Vec` +* Implement `PartialEq` 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 diff --git a/Cargo.toml b/Cargo.toml index 4a31b09..66d9b16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 70b370a..eea28fa 100644 --- a/README.md +++ b/README.md @@ -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** → | 11.964 | 1 0.392 | 7.963 | 18.609 | -| **Total deviation (p.c.)** → | +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** → | 14.456 | 11.624 | 8.800 | 21.509 | +| **Total deviation (p.c.)** → | +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 – 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::, - pub response_buf: Slot::, + pub request_buf: Slot, + pub response_buf: Slot, } 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 diff --git a/oct-benchmarks/Cargo.toml b/oct-benchmarks/Cargo.toml index 22ab857..62b5755 100644 --- a/oct-benchmarks/Cargo.toml +++ b/oct-benchmarks/Cargo.toml @@ -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" diff --git a/oct-benchmarks/src/main.rs b/oct-benchmarks/src/main.rs index ee82e4e..86a8b60 100644 --- a/oct-benchmarks/src/main.rs +++ b/oct-benchmarks/src/main.rs @@ -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::(); + + let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &random::()).unwrap(); + } + } + + borsh: { + const ITEM_SIZE: usize = size_of::(); + + let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer::(&mut buf, &random::().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::().encode(&mut stream).unwrap(); + } + } + + postcard: { + const ITEM_SIZE: usize = size_of::(); + + let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&random::(), &mut buf).unwrap(); + } + } + } + encode_struct_unit: { bincode: { use bincode::serialize_into; diff --git a/oct-macros/Cargo.toml b/oct-macros/Cargo.toml index f1e3cf9..3e34fce 100644 --- a/oct-macros/Cargo.toml +++ b/oct-macros/Cargo.toml @@ -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/" diff --git a/oct-macros/src/discriminants/mod.rs b/oct-macros/src/discriminants/mod.rs index ea2aebd..ca238e9 100644 --- a/oct-macros/src/discriminants/mod.rs +++ b/oct-macros/src/discriminants/mod.rs @@ -6,7 +6,7 @@ // can obtain one at: // . -use std::borrow::Borrow; +use core::borrow::Borrow; use proc_macro2::Span; use syn::{Expr, Lit, LitInt, Variant}; diff --git a/oct-macros/src/generic_name/mod.rs b/oct-macros/src/generic_name/mod.rs index b8d6bf2..9bdfc5d 100644 --- a/oct-macros/src/generic_name/mod.rs +++ b/oct-macros/src/generic_name/mod.rs @@ -6,10 +6,10 @@ // can obtain one at: // . +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, diff --git a/oct-macros/src/impl_derive_macro.rs b/oct-macros/src/impl_derive_macro.rs index 61d0344..b4eca6f 100644 --- a/oct-macros/src/impl_derive_macro.rs +++ b/oct-macros/src/impl_derive_macro.rs @@ -20,11 +20,11 @@ use syn::{ }; pub fn impl_derive_macro( - input: DeriveInput, - trait_path: Path, - r#unsafe_token: Option, - struct_body: S, - enum_body: E, + input: DeriveInput, + trait_path: Path, + unsafe_token: Option, + 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; diff --git a/oct-macros/src/impls/decode_enum.rs b/oct-macros/src/impls/decode_enum.rs index 95fabfb..51b76cd 100644 --- a/oct-macros/src/impls/decode_enum.rs +++ b/oct-macros/src/impls/decode_enum.rs @@ -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 { @@ -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) } } } diff --git a/oct-macros/src/impls/decode_struct.rs b/oct-macros/src/impls/decode_struct.rs index a183413..fd47ed5 100644 --- a/oct-macros/src/impls/decode_struct.rs +++ b/oct-macros/src/impls/decode_struct.rs @@ -6,10 +6,10 @@ // can obtain one at: // . +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 { diff --git a/oct-macros/src/impls/encode_enum.rs b/oct-macros/src/impls/encode_enum.rs index 70855ca..7ae3de7 100644 --- a/oct-macros/src/impls/encode_enum.rs +++ b/oct-macros/src/impls/encode_enum.rs @@ -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] diff --git a/oct-macros/src/impls/encode_struct.rs b/oct-macros/src/impls/encode_struct.rs index 51d6174..4df5bbd 100644 --- a/oct-macros/src/impls/encode_struct.rs +++ b/oct-macros/src/impls/encode_struct.rs @@ -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) diff --git a/oct-macros/src/impls/sized_encode_enum.rs b/oct-macros/src/impls/sized_encode_enum.rs index 3c12f24..f01c4de 100644 --- a/oct-macros/src/impls/sized_encode_enum.rs +++ b/oct-macros/src/impls/sized_encode_enum.rs @@ -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] diff --git a/oct-macros/src/repr/mod.rs b/oct-macros/src/repr/mod.rs index 55c0cd8..c857ec0 100644 --- a/oct-macros/src/repr/mod.rs +++ b/oct-macros/src/repr/mod.rs @@ -6,9 +6,9 @@ // can obtain one at: // . +use core::iter; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; -use std::iter; use syn::{ Attribute, Ident, diff --git a/oct/Cargo.toml b/oct/Cargo.toml index 7b5628a..69bf4f1 100644 --- a/oct/Cargo.toml +++ b/oct/Cargo.toml @@ -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 diff --git a/oct/src/decode/decode/mod.rs b/oct/src/decode/decode/mod.rs index 8dc5b6a..550719c 100644 --- a/oct/src/decode/decode/mod.rs +++ b/oct/src/decode/decode/mod.rs @@ -7,7 +7,7 @@ // . #[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 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 Decode for Arc { } } +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl Decode for BinaryHeap { + type Error = as Decode>::Error; + + #[inline(always)] + fn decode(input: &mut Input) -> Result { + 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 Decode for Bound { - type Error = EnumDecodeError; + type Error = EnumDecodeError::Error, T::Error>; #[inline(always)] fn decode(input: &mut Input) -> Result { @@ -168,7 +192,7 @@ impl Decode for Bound { 0x2 => Self::Unbounded, - value => return Err(EnumDecodeError::UnassignedDiscriminant { value }), + value => return Err(EnumDecodeError::UnassignedDiscriminant(value)), }; Ok(this) @@ -189,6 +213,30 @@ impl Decode for Box { } } +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl Decode for CString { + type Error = 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 { + 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 Decode for Cell { type Error = T::Error; @@ -201,6 +249,15 @@ impl Decode for Cell { } } +impl Decode for c_void { + type Error = Infallible; + + #[inline(always)] + fn decode(_input: &mut Input) -> Result { + 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 Decode for HashMap +where + K: Decode + Eq + Hash, + V: Decode, + S: BuildHasher + Default, +{ + type Error = CollectionDecodeError>; + + #[inline] + fn decode(input: &mut Input) -> Result { + 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 Decode for HashSet +where + K: Decode + Eq + Hash, + S: BuildHasher + Default, +{ + type Error = CollectionDecodeError>; + + #[inline] + fn decode(input: &mut Input) -> Result { + 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 { - 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; + type Error = EnumDecodeError::Error, Infallible>; #[inline] fn decode(input: &mut Input) -> Result { - 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 Decode for LinkedList { + type Error = CollectionDecodeError>; + + #[inline] + fn decode(input: &mut Input) -> Result { + 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 Decode for Mutex { @@ -357,25 +492,13 @@ impl Decode for Mutex { } } -#[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 { - panic!("cannot deserialise `!` as it cannot be serialised to begin with") - } -} - impl Decode for Option { type Error = T::Error; #[expect(clippy::if_then_some_else_none)] // ??? #[inline] fn decode(input: &mut Input) -> Result { - let sign = bool::decode(input) - .map_err::(|_e| unreachable!())?; + let Ok(sign) = bool::decode(input); let this = if sign { Some(Decode::decode(input)?) @@ -387,6 +510,20 @@ impl Decode for Option { } } +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl Decode for OsString { + type Error = ::Error; + + #[inline(always)] + fn decode(input: &mut Input) -> Result { + let s: alloc::string::String = Decode::decode(input)?; + + let this = s.into(); + Ok(this) + } +} + impl Decode for PhantomData { type Error = Infallible; @@ -499,7 +636,7 @@ where T: Decode, E: Decode>, { - type Error = EnumDecodeError; + type Error = EnumDecodeError::Error, Err>; #[inline] fn decode(input: &mut Input) -> Result { @@ -550,7 +687,7 @@ impl Decode for Saturating { } impl Decode for SocketAddr { - type Error = EnumDecodeError; + type Error = EnumDecodeError::Error, Infallible>; #[inline] fn decode(input: &mut Input) -> Result { @@ -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; + + #[inline] + fn decode(input: &mut Input) -> Result { + 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 Decode for alloc::vec::Vec { + type Error = CollectionDecodeError>; + + #[inline] + fn decode(input: &mut Input) -> Result { + 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 Decode for Wrapping { 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, } diff --git a/oct/src/decode/decode/test.rs b/oct/src/decode/decode/test.rs new file mode 100644 index 0000000..8ee9031 --- /dev/null +++ b/oct/src/decode/decode/test.rs @@ -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: +// . + +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 { + [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 _ = 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 _ = as Decode>::decode(&mut stream).unwrap_err(); +} diff --git a/oct/src/decode/decode/tests.rs b/oct/src/decode/decode/tests.rs deleted file mode 100644 index da7195b..0000000 --- a/oct/src/decode/decode/tests.rs +++ /dev/null @@ -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: -// . - -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, 0x6> { -// [0x02, 0x00, 0xBB, 0xAA, 0xDD, 0xCC] => Lid([0xAA_BB, 0xCC_DD].into()), -// } - -// Lid { -// [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 }, - } - } -} diff --git a/oct/src/decode/decode_borrowed/mod.rs b/oct/src/decode/decode_borrowed/mod.rs index 761983b..839c305 100644 --- a/oct/src/decode/decode_borrowed/mod.rs +++ b/oct/src/decode/decode_borrowed/mod.rs @@ -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 <Self as [Decode]>::[decode](Decode::decode) 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: Borrow + Decode { } impl DecodeBorrowed for T { } @@ -43,6 +49,22 @@ impl DecodeBorrowed for Arc { } #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl DecodeBorrowed for Box { } +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed for CString { } + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl DecodeBorrowed for OsString { } + #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl DecodeBorrowed for Rc { } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed for alloc::string::String { } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed<[T]> for alloc::vec::Vec { } diff --git a/oct/src/decode/input/mod.rs b/oct/src/decode/input/mod.rs index f677fc8..d3428ab 100644 --- a/oct/src/decode/input/mod.rs +++ b/oct/src/decode/input/mod.rs @@ -6,8 +6,13 @@ // can obtain one at: // . +#[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 + } } diff --git a/oct/src/decode/input/test.rs b/oct/src/decode/input/test.rs new file mode 100644 index 0000000..fa65ed1 --- /dev/null +++ b/oct/src/decode/input/test.rs @@ -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: +// . + +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())); +} diff --git a/oct/src/encode/encode/mod.rs b/oct/src/encode/encode/mod.rs index 2f51d89..53d9b87 100644 --- a/oct/src/encode/encode/mod.rs +++ b/oct/src/encode/encode/mod.rs @@ -7,7 +7,7 @@ // . #[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 Encode for Arc { } } +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl Encode for BinaryHeap { + 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 = ::Error; @@ -248,6 +264,40 @@ impl Encode for Box { } } +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 = ::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 Encode for Cell { type Error = T::Error; @@ -277,28 +327,6 @@ impl 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 = ::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; + type Error = EnumEncodeError<::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 Encode for LazyLock { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Encode for LinkedList { - type Error = CollectionEncodeError; + type Error = CollectionEncodeError>; #[inline(always)] fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { @@ -491,7 +519,7 @@ impl Encode for LinkedList { 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 Encode for Mutex { } } -// Especially useful for `Result`. -// **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 Encode for Option { type Error = T::Error; @@ -554,6 +568,59 @@ impl Encode for Option { } } +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl Encode for OsStr { + type Error = CollectionEncodeError; + + /// 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 = ::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 Encode for PhantomData { type Error = Infallible; @@ -784,7 +851,7 @@ impl Encode for str { impl Encode for String { type Error = ::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, } diff --git a/oct/src/encode/encode/test.rs b/oct/src/encode/encode/test.rs new file mode 100644 index 0000000..c812272 --- /dev/null +++ b/oct/src/encode/encode/test.rs @@ -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: +// . + +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 { + 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, + ]), + } + } +} diff --git a/oct/src/encode/encode/tests.rs b/oct/src/encode/encode/tests.rs deleted file mode 100644 index d3f6c9c..0000000 --- a/oct/src/encode/encode/tests.rs +++ /dev/null @@ -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: -// . - -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 { - 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, - ], - } - } -} diff --git a/oct/src/encode/mod.rs b/oct/src/encode/mod.rs index 4184688..ae64b73 100644 --- a/oct/src/encode/mod.rs +++ b/oct/src/encode/mod.rs @@ -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)] diff --git a/oct/src/encode/output/mod.rs b/oct/src/encode/output/mod.rs index 7eebf4c..91bbb08 100644 --- a/oct/src/encode/output/mod.rs +++ b/oct/src/encode/output/mod.rs @@ -6,14 +6,17 @@ // can obtain one at: // . +#[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 - } -} diff --git a/oct/src/encode/output/test.rs b/oct/src/encode/output/test.rs new file mode 100644 index 0000000..70f5d59 --- /dev/null +++ b/oct/src/encode/output/test.rs @@ -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: +// . + +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(())); +} diff --git a/oct/src/encode/sized_encode/mod.rs b/oct/src/encode/sized_encode/mod.rs index 2048f5f..01db11c 100644 --- a/oct/src/encode/sized_encode/mod.rs +++ b/oct/src/encode/sized_encode/mod.rs @@ -7,12 +7,13 @@ // . #[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 SizedEncode for Box { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } +impl SizedEncode for c_void { + const MAX_ENCODED_SIZE: usize = 0x0; +} + impl SizedEncode for Cell { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } @@ -178,12 +184,6 @@ impl SizedEncode for Mutex { 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 SizedEncode for Option { 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, } diff --git a/oct/src/encode/sized_encode/tests.rs b/oct/src/encode/sized_encode/test.rs similarity index 96% rename from oct/src/encode/sized_encode/tests.rs rename to oct/src/encode/sized_encode/test.rs index 27e274e..8836750 100644 --- a/oct/src/encode/sized_encode/tests.rs +++ b/oct/src/encode/sized_encode/test.rs @@ -6,10 +6,9 @@ // can obtain one at: // . -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$(,)?) => {{ diff --git a/oct/src/error/char_decode_error.rs b/oct/src/error/char_decode_error.rs index 3869784..a1baf2f 100644 --- a/oct/src/error/char_decode_error.rs +++ b/oct/src/error/char_decode_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for CharDecodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/collection_decode_error.rs b/oct/src/error/collection_decode_error.rs index 31171bf..cadf44f 100644 --- a/oct/src/error/collection_decode_error.rs +++ b/oct/src/error/collection_decode_error.rs @@ -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 { /// The collection length could not be decoded or was invalid. @@ -63,6 +64,15 @@ where } } +impl From for CollectionDecodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} + impl From> for Infallible where L: Into, diff --git a/oct/src/error/collection_encode_error.rs b/oct/src/error/collection_encode_error.rs index 6bdff20..564b788 100644 --- a/oct/src/error/collection_encode_error.rs +++ b/oct/src/error/collection_encode_error.rs @@ -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 { /// The collection length could not be encoded. @@ -56,6 +57,15 @@ where } } +impl From for CollectionEncodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} + impl From> for Infallible where L: Into, diff --git a/oct/src/error/enum_decode_error.rs b/oct/src/error/enum_decode_error.rs index 7dcc251..e65440d 100644 --- a/oct/src/error/enum_decode_error.rs +++ b/oct/src/error/enum_decode_error.rs @@ -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 { +pub enum EnumDecodeError { /// 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 Display for EnumDecodeError +impl Display for EnumDecodeError where - D: Decode + 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 Error for EnumDecodeError +impl Error for EnumDecodeError where - D: Debug + Decode + Display, + T: Debug + Display, + D: Error + 'static, F: Error + 'static, { #[inline] @@ -69,13 +69,22 @@ where } } -impl From> for Infallible +impl From for EnumDecodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} + +impl From> for Infallible where - D: Decode>, + T: Decode>, F: Into, { #[inline(always)] - fn from(_value: EnumDecodeError) -> Self { + fn from(_value: EnumDecodeError) -> Self { unreachable!() } } diff --git a/oct/src/error/enum_encode_error.rs b/oct/src/error/enum_encode_error.rs index 00fd4d9..105abbf 100644 --- a/oct/src/error/enum_encode_error.rs +++ b/oct/src/error/enum_encode_error.rs @@ -6,18 +6,17 @@ // can obtain one at: // . -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 { +pub enum EnumEncodeError { /// 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 { impl Display for EnumEncodeError where - D: Display + Encode, + D: Display, F: Display, { #[inline] @@ -42,7 +41,7 @@ where impl Error for EnumEncodeError where - D: Debug + Display + Encode, + D: Error + 'static, F: Error + 'static, { #[inline] @@ -55,9 +54,18 @@ where } } +impl From for EnumEncodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} + impl From> for Infallible where - D: Encode>, + D: Into, F: Into, { #[inline(always)] diff --git a/oct/src/error/generic_decode_error.rs b/oct/src/error/generic_decode_error.rs index 58ee540..af03f16 100644 --- a/oct/src/error/generic_decode_error.rs +++ b/oct/src/error/generic_decode_error.rs @@ -6,16 +6,15 @@ // can obtain one at: // . -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 From> for GenericDecodeError +impl From> for GenericDecodeError where - D: Decode> + PrimitiveDiscriminant, + T: PrimRepr, + D: Into, F: Into, { #[inline(always)] - fn from(value: EnumDecodeError) -> Self { + fn from(value: EnumDecodeError) -> 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 for GenericDecodeError { } } -impl From for GenericDecodeError { - #[inline(always)] - fn from(value: StringError) -> Self { - Self::BadString(value) - } -} - #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] impl From for GenericDecodeError { @@ -176,3 +166,10 @@ impl From for GenericDecodeError { Self::NarrowSystemTime(value) } } + +impl From for GenericDecodeError { + #[inline(always)] + fn from(value: Utf8Error) -> Self { + Self::BadString(value) + } +} diff --git a/oct/src/error/generic_encode_error.rs b/oct/src/error/generic_encode_error.rs index fa66aa4..91517cd 100644 --- a/oct/src/error/generic_encode_error.rs +++ b/oct/src/error/generic_encode_error.rs @@ -6,7 +6,6 @@ // can obtain one at: // . -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 From> for GenericEncodeError where - D: Encode>, + D: Into, F: Into, { #[inline(always)] diff --git a/oct/src/error/input_error.rs b/oct/src/error/input_error.rs index 8498fd7..bfd2d5c 100644 --- a/oct/src/error/input_error.rs +++ b/oct/src/error/input_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for InputError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/isize_encode_error.rs b/oct/src/error/isize_encode_error.rs index e623966..98d3d73 100644 --- a/oct/src/error/isize_encode_error.rs +++ b/oct/src/error/isize_encode_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for IsizeEncodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/item_decode_error.rs b/oct/src/error/item_decode_error.rs index 2998a27..23f0afe 100644 --- a/oct/src/error/item_decode_error.rs +++ b/oct/src/error/item_decode_error.rs @@ -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 { /// The index of the invalid item. @@ -45,6 +46,15 @@ where } } +impl From for ItemDecodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} + impl From> for Infallible { #[inline(always)] fn from(_value: ItemDecodeError) -> Self { diff --git a/oct/src/error/item_encode_error.rs b/oct/src/error/item_encode_error.rs index ac47529..c2ba0a6 100644 --- a/oct/src/error/item_encode_error.rs +++ b/oct/src/error/item_encode_error.rs @@ -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 { /// The index of the invalid item. @@ -45,6 +46,15 @@ where } } +impl From for ItemEncodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} + impl> From> for Infallible { #[inline(always)] fn from(_value: ItemEncodeError) -> Self { diff --git a/oct/src/error/length_error.rs b/oct/src/error/length_error.rs index 10bf23f..6f0747a 100644 --- a/oct/src/error/length_error.rs +++ b/oct/src/error/length_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for LengthError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/mod.rs b/oct/src/error/mod.rs index badb7af..9875200 100644 --- a/oct/src/error/mod.rs +++ b/oct/src/error/mod.rs @@ -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); diff --git a/oct/src/error/non_zero_decode_error.rs b/oct/src/error/non_zero_decode_error.rs index 93e8033..0327dec 100644 --- a/oct/src/error/non_zero_decode_error.rs +++ b/oct/src/error/non_zero_decode_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for NonZeroDecodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/output_error.rs b/oct/src/error/output_error.rs index cc0a0fe..3e20dbf 100644 --- a/oct/src/error/output_error.rs +++ b/oct/src/error/output_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for OutputError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/ref_cell_encode_error.rs b/oct/src/error/ref_cell_encode_error.rs index aa0bcab..a43c206 100644 --- a/oct/src/error/ref_cell_encode_error.rs +++ b/oct/src/error/ref_cell_encode_error.rs @@ -7,8 +7,10 @@ // . 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 Error for RefCellEncodeError { } } } + +impl From for RefCellEncodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/string_error.rs b/oct/src/error/string_error.rs deleted file mode 100644 index ad66f0b..0000000 --- a/oct/src/error/string_error.rs +++ /dev/null @@ -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: -// . - -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), - } - } -} diff --git a/oct/src/error/system_time_decode_error.rs b/oct/src/error/system_time_decode_error.rs index bd90e17..27b4a97 100644 --- a/oct/src/error/system_time_decode_error.rs +++ b/oct/src/error/system_time_decode_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for SystemTimeDecodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/usize_encode_error.rs b/oct/src/error/usize_encode_error.rs index ecdab2c..fae1a2d 100644 --- a/oct/src/error/usize_encode_error.rs +++ b/oct/src/error/usize_encode_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for UsizeEncodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/error/utf8_error.rs b/oct/src/error/utf8_error.rs index 148db45..3f975df 100644 --- a/oct/src/error/utf8_error.rs +++ b/oct/src/error/utf8_error.rs @@ -6,8 +6,10 @@ // can obtain one at: // . +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 for Utf8Error { + #[inline(always)] + fn from(_value: Infallible) -> Self { + // SAFETY: `Infallible` objects can never be con- + // structed. + unsafe { unreachable_unchecked() }; + } +} diff --git a/oct/src/lib.rs b/oct/src/lib.rs index 6c23f80..a633f22 100644 --- a/oct/src/lib.rs +++ b/oct/src/lib.rs @@ -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** → | 11.964 | 10.392 | 7.963 | 18.609 | -//! | **Total deviation (p.c.)** → | +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** → | 14.456 | 11.624 | 8.800 | 21.509 | +//! | **Total deviation (p.c.)** → | +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::, -//! pub response_buf: Slot::, +//! pub request_buf: Slot, +//! pub response_buf: Slot, //! } //! //! 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); diff --git a/oct/src/prim_discriminant/mod.rs b/oct/src/prim_discriminant/mod.rs new file mode 100644 index 0000000..455abde --- /dev/null +++ b/oct/src/prim_discriminant/mod.rs @@ -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: +// . + +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 From 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); diff --git a/oct/src/prim_discriminant/test.rs b/oct/src/prim_discriminant/test.rs new file mode 100644 index 0000000..2a96b23 --- /dev/null +++ b/oct/src/prim_discriminant/test.rs @@ -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: +// . + +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"); +} diff --git a/oct/src/prim_repr/mod.rs b/oct/src/prim_repr/mod.rs new file mode 100644 index 0000000..6311fe7 --- /dev/null +++ b/oct/src/prim_repr/mod.rs @@ -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: +// . + +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); \ No newline at end of file diff --git a/oct/src/primitive_discriminant/mod.rs b/oct/src/primitive_discriminant/mod.rs deleted file mode 100644 index e32c25a..0000000 --- a/oct/src/primitive_discriminant/mod.rs +++ /dev/null @@ -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: -// . - -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); diff --git a/oct/src/slot/slot/mod.rs b/oct/src/slot/slot/mod.rs index 0cf2636..daee026 100644 --- a/oct/src/slot/slot/mod.rs +++ b/oct/src/slot/slot/mod.rs @@ -7,7 +7,7 @@ // . #[cfg(test)] -mod tests; +mod test; use crate::decode::{Decode, Input}; use crate::encode::{Encode, Output, SizedEncode}; @@ -93,7 +93,7 @@ impl Slot { let buf = { let buf = ptr::slice_from_raw_parts_mut(ptr, cap); - Box::from_raw(buf) + unsafe { Box::from_raw(buf) } }; Self { diff --git a/oct/src/slot/slot/tests.rs b/oct/src/slot/slot/test.rs similarity index 100% rename from oct/src/slot/slot/tests.rs rename to oct/src/slot/slot/test.rs diff --git a/oct/src/string/string/cmp.rs b/oct/src/string/string/cmp.rs deleted file mode 100644 index 21c5d54..0000000 --- a/oct/src/string/string/cmp.rs +++ /dev/null @@ -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: -// . - -use crate::string::String; - -use core::cmp::Ordering; - -impl Ord for String { - #[inline(always)] - fn cmp(&self, other: &Self) -> Ordering { - self.as_str().cmp(other.as_str()) - } -} - -impl PartialEq> for String { - #[inline(always)] - fn eq(&self, other: &String) -> bool { - self.as_str() == other.as_str() - } -} - -impl PartialEq<&str> for String { - #[inline(always)] - fn eq(&self, other: &&str) -> bool { - self.as_str() == *other - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl PartialEq for String { - #[inline(always)] - fn eq(&self, other: &alloc::string::String) -> bool { - self.as_str() == other.as_str() - } -} - -impl PartialOrd> for String { - #[inline(always)] - fn partial_cmp(&self, other: &String) -> Option { - self.as_str().partial_cmp(other.as_str()) - } -} - -impl PartialOrd<&str> for String { - #[inline(always)] - fn partial_cmp(&self, other: &&str) -> Option { - self.as_str().partial_cmp(*other) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl PartialOrd for String { - #[inline(always)] - fn partial_cmp(&self, other: &alloc::string::String) -> Option { - self.as_str().partial_cmp(other.as_str()) - } -} diff --git a/oct/src/string/string/code.rs b/oct/src/string/string/code.rs deleted file mode 100644 index 258c4fa..0000000 --- a/oct/src/string/string/code.rs +++ /dev/null @@ -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: -// . - -use crate::string::String; -use crate::decode::{Decode, DecodeBorrowed, Input}; -use crate::encode::{Encode, Output, SizedEncode}; -use crate::error::{CollectionDecodeError, LengthError, StringError, Utf8Error}; - -impl Decode for String { - type Error = CollectionDecodeError; - - #[inline] - fn decode(input: &mut Input) -> Result { - 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 DecodeBorrowed for String { } - -impl Encode for String { - type Error = ::Error; - - #[inline(always)] - fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { - self.as_str().encode(output) - } -} - -impl SizedEncode for String { - const MAX_ENCODED_SIZE: usize = - usize::MAX_ENCODED_SIZE - + u8::MAX_ENCODED_SIZE * N; -} diff --git a/oct/src/string/string/conv.rs b/oct/src/string/string/conv.rs deleted file mode 100644 index c267240..0000000 --- a/oct/src/string/string/conv.rs +++ /dev/null @@ -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: -// . - -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 String { - /// 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; 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 { - 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 { - let this = ManuallyDrop::new(self); - - // SAFETY: `ManuallyDrop` is transparent to `T`. - // We also aren't dropping `this`, so we can safely - // move out of it. - unsafe { (&raw const this).cast::>().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 { - 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 AsMut for String { - #[inline(always)] - fn as_mut(&mut self) -> &mut str { - self.as_mut_str() - } -} - -#[cfg(feature = "std")] -#[cfg_attr(doc, doc(cfg(feature = "std")))] -impl AsRef for String { - #[inline(always)] - fn as_ref(&self) -> &OsStr { - self.as_str().as_ref() - } -} - -#[cfg(feature = "std")] -#[cfg_attr(doc, doc(cfg(feature = "std")))] -impl AsRef for String { - #[inline(always)] - fn as_ref(&self) -> &Path { - self.as_str().as_ref() - } -} - -impl AsRef for String { - #[inline(always)] - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl AsRef<[u8]> for String { - #[inline(always)] - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl Borrow for String { - #[inline(always)] - fn borrow(&self) -> &str { - self.as_str() - } -} - -impl BorrowMut for String { - #[inline(always)] - fn borrow_mut(&mut self) -> &mut str { - self.as_mut_str() - } -} - -impl Deref for String { - type Target = str; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - self.as_str() - } -} - -impl DerefMut for String { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut_str() - } -} - -impl FromStr for String { - type Err = LengthError; - - #[inline] - fn from_str(s: &str) -> Result { - Self::new(s) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(doc, doc(cfg(feature = "std")))] -impl ToSocketAddrs for String { - type Iter = ::Iter; - - #[inline(always)] - fn to_socket_addrs(&self) -> std::io::Result { - self.as_str().to_socket_addrs() - } -} - -impl TryFrom for String { - type Error = ::Err; - - #[inline(always)] - fn try_from(value: char) -> Result { - let mut buf = [0x00; 0x4]; - let s = value.encode_utf8(&mut buf); - - s.parse() - } -} - -impl TryFrom<&str> for String { - type Error = ::Err; - - #[inline(always)] - fn try_from(value: &str) -> Result { - Self::new(value) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl TryFrom for String { - type Error = ::Err; - - #[inline(always)] - fn try_from(value: alloc::string::String) -> Result { - Self::new(&value) - } -} - -/// See [`into_boxed_str`](String::into_boxed_str). -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl From> for Box { - #[inline(always)] - fn from(value: String) -> Self { - value.into_boxed_str() - } -} - -/// See [`into_string`](String::into_string). -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl From> for alloc::string::String { - #[inline(always)] - fn from(value: String) -> Self { - value.into_string() - } -} diff --git a/oct/src/string/string/mod.rs b/oct/src/string/string/mod.rs index 6e2103b..8b5a6d4 100644 --- a/oct/src/string/string/mod.rs +++ b/oct/src/string/string/mod.rs @@ -7,33 +7,41 @@ // . #[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(Vec); +#[derive(Clone, Copy)] +pub struct String { + len: usize, + buf: [u8; N], +} impl String { - /// 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 { - 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) -> Result)> { + 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) -> 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` 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 { + let (buf, len) = self.into_raw_parts(); + + // SAFETY: `MaybeUninit` is transparent to `u8`. + let buf = unsafe { (&raw const buf).cast::<[MaybeUninit; 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 { + 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 AsMut for String { + #[inline(always)] + fn as_mut(&mut self) -> &mut str { + self.as_mut_str() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl AsRef for String { + #[inline(always)] + fn as_ref(&self) -> &OsStr { + self.as_str().as_ref() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl AsRef for String { + #[inline(always)] + fn as_ref(&self) -> &Path { + self.as_str().as_ref() + } +} + +impl AsRef for String { + #[inline(always)] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef<[u8]> for String { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Borrow for String { + #[inline(always)] + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl BorrowMut for String { + #[inline(always)] + fn borrow_mut(&mut self) -> &mut str { + self.as_mut_str() + } +} + +impl Deref for String { + type Target = str; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl Default for String { + #[inline(always)] + fn default() -> Self { + let buf = [Default::default(); N]; + let len = 0x0; + + unsafe { Self::from_raw_parts(buf, len) } + } +} + +impl DerefMut for String { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_str() } } @@ -114,6 +437,24 @@ impl Debug for String { } } +impl Decode for String { + type Error = CollectionDecodeError; + + #[inline] + fn decode(input: &mut decode::Input) -> Result { + let v = Vec::::decode(input) + .map_err(|e| { + let CollectionDecodeError::BadLength(e) = e; + CollectionDecodeError::BadLength(e) + })?; + + Self::from_utf8(v) + .map_err(|(e, ..)| CollectionDecodeError::BadItem(e)) + } +} + +impl DecodeBorrowed for String { } + impl Display for String { #[inline(always)] fn fmt(&self, f: &mut Formatter) -> fmt::Result { @@ -121,6 +462,17 @@ impl Display for String { } } +impl Encode for String { + type Error = ::Error; + + #[inline(always)] + fn encode(&self, output: &mut encode::Output) -> Result<(), Self::Error> { + self.as_str().encode(output) + } +} + +impl Eq for String { } + impl FromIterator for String { #[inline] fn from_iter>(iter: I) -> Self { @@ -147,6 +499,15 @@ impl FromIterator for String { } } +impl FromStr for String { + type Err = LengthError; + + #[inline] + fn from_str(s: &str) -> Result { + Self::new(s) + } +} + impl Hash for String { #[inline(always)] fn hash(&self, state: &mut H) { @@ -169,3 +530,125 @@ impl, const N: usize> IndexMut for String { self.get_mut(index).unwrap() } } + +impl Ord for String { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl PartialEq> for String { + #[inline(always)] + fn eq(&self, other: &String) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq for String { + #[inline(always)] + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl PartialEq<&str> for String { + #[inline(always)] + fn eq(&self, other: &&str) -> bool { + self == *other + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl PartialEq for String { + #[inline(always)] + fn eq(&self, other: &alloc::string::String) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialOrd> for String { + #[inline(always)] + fn partial_cmp(&self, other: &String) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl SizedEncode for String { + 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 ToSocketAddrs for String { + type Iter = ::Iter; + + #[inline(always)] + fn to_socket_addrs(&self) -> std::io::Result { + self.as_str().to_socket_addrs() + } +} + +impl TryFrom for String { + type Error = ::Err; + + #[inline(always)] + fn try_from(value: char) -> Result { + let mut buf = [0x00; 0x4]; + let s = value.encode_utf8(&mut buf); + + s.parse() + } +} + +impl TryFrom<&str> for String { + type Error = ::Err; + + #[inline(always)] + fn try_from(value: &str) -> Result { + Self::new(value) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl TryFrom for String { + type Error = ::Err; + + #[inline(always)] + fn try_from(value: alloc::string::String) -> Result { + Self::new(&value) + } +} + +/// See [`into_boxed_str`](String::into_boxed_str). +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl From> for Box { + #[inline(always)] + fn from(value: String) -> Self { + value.into_boxed_str() + } +} + +/// See [`into_string`](String::into_string). +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl From> for alloc::string::String { + #[inline(always)] + fn from(value: String) -> Self { + value.into_string() + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl PartialEq> for alloc::string::String { + #[inline(always)] + fn eq(&self, other: &String) -> bool { + self.as_str() == other.as_str() + } +} diff --git a/oct/src/string/string/test.rs b/oct/src/string/string/test.rs new file mode 100644 index 0000000..544a0ee --- /dev/null +++ b/oct/src/string/string/test.rs @@ -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: +// . + +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(..), + ); +} diff --git a/oct/src/string/string/tests.rs b/oct/src/string/string/tests.rs deleted file mode 100644 index 3995afc..0000000 --- a/oct/src/string/string/tests.rs +++ /dev/null @@ -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: -// . - -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(..), - ); -} diff --git a/oct/src/vec/into_iter/mod.rs b/oct/src/vec/into_iter/mod.rs index ebf5123..13a9af1 100644 --- a/oct/src/vec/into_iter/mod.rs +++ b/oct/src/vec/into_iter/mod.rs @@ -7,7 +7,7 @@ // . #[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 core::slice::{[Iter](core::slice::Iter), [IterMut](core::slice::IterMut)} types are used instead. #[must_use] pub struct IntoIter { - buf: [MaybeUninit; N], - - pos: usize, len: usize, + pos: usize, + + buf: [MaybeUninit; N], } impl IntoIter { - /// Constructs a new, fixed-size iterator. + /// Constructs a new, size-constrained iterator. #[inline(always)] pub(crate) const unsafe fn new(buf: [MaybeUninit; 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 AsRef<[T]> for IntoIter { impl Clone for IntoIter { #[inline] fn clone(&self) -> Self { - let mut buf = [const { MaybeUninit::::uninit() };N]; + let mut buf = [const { MaybeUninit::::uninit() }; N]; let Self { pos, len, .. } = *self; let start = pos; @@ -95,7 +95,7 @@ impl Clone for IntoIter { } } - Self { buf, pos, len } + Self { len, pos, buf } } } diff --git a/oct/src/vec/into_iter/tests.rs b/oct/src/vec/into_iter/test.rs similarity index 98% rename from oct/src/vec/into_iter/tests.rs rename to oct/src/vec/into_iter/test.rs index 90844f0..fc61df7 100644 --- a/oct/src/vec/into_iter/tests.rs +++ b/oct/src/vec/into_iter/test.rs @@ -6,9 +6,9 @@ // can obtain one at: // . +use core::str::FromStr; use oct::string::String; use oct::vec::Vec; -use std::str::FromStr; #[test] fn test_vec_iter_clone() { diff --git a/oct/src/vec/vec/cmp.rs b/oct/src/vec/vec/cmp.rs deleted file mode 100644 index 257db91..0000000 --- a/oct/src/vec/vec/cmp.rs +++ /dev/null @@ -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: -// . - -use crate::vec::Vec; - -use core::cmp::Ordering; - -impl Eq for Vec { } - -impl Ord for Vec { - #[inline(always)] - fn cmp(&self, other: &Self) -> Ordering { - self.as_slice().cmp(other.as_slice()) - } -} - -impl, U: PartialEq, const N: usize, const M: usize> PartialEq> for Vec { - #[inline(always)] - fn eq(&self, other: &Vec) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl, U: PartialEq, const N: usize, const M: usize> PartialEq<[U; M]> for Vec { - #[inline(always)] - fn eq(&self, other: &[U; M]) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl, U: PartialEq, const N: usize> PartialEq<&[U]> for Vec { - #[inline(always)] - fn eq(&self, other: &&[U]) -> bool { - self.as_slice() == *other - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl, U: PartialEq, const N: usize> PartialEq> for Vec { - #[inline(always)] - fn eq(&self, other: &alloc::vec::Vec) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl PartialOrd> for Vec { - #[inline(always)] - fn partial_cmp(&self, other: &Vec) -> Option { - self.as_slice().partial_cmp(other.as_slice()) - } -} - -impl PartialOrd<[T; M]> for Vec { - #[inline(always)] - fn partial_cmp(&self, other: &[T; M]) -> Option { - self.as_slice().partial_cmp(other.as_slice()) - } -} - -impl PartialOrd<&[T]> for Vec { - #[inline(always)] - fn partial_cmp(&self, other: &&[T]) -> Option { - self.as_slice().partial_cmp(*other) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl PartialOrd> for Vec { - #[inline(always)] - fn partial_cmp(&self, other: &alloc::vec::Vec) -> Option { - self.as_slice().partial_cmp(other.as_slice()) - } -} diff --git a/oct/src/vec/vec/code.rs b/oct/src/vec/vec/code.rs deleted file mode 100644 index cbbcf95..0000000 --- a/oct/src/vec/vec/code.rs +++ /dev/null @@ -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: -// . - -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 Decode for Vec { - type Error = CollectionDecodeError>; - - #[inline] - fn decode(input: &mut Input) -> Result { - let len = Decode::decode(input).unwrap(); - if len > N { - return Err(CollectionDecodeError::BadLength(LengthError { - remaining: N, - count: len, - })); - } - - let mut buf = [const { MaybeUninit::::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 DecodeBorrowed<[T]> for Vec { } - -impl Encode for Vec { - type Error = <[T] as Encode>::Error; - - #[inline(always)] - fn encode(&self, output: &mut Output) -> Result<(), Self::Error> { - self.as_slice().encode(output) - } -} - -impl SizedEncode for Vec { - const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N; -} diff --git a/oct/src/vec/vec/conv.rs b/oct/src/vec/vec/conv.rs deleted file mode 100644 index 9ca4046..0000000 --- a/oct/src/vec/vec/conv.rs +++ /dev/null @@ -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: -// . - -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 Vec { - /// 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; 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; N], usize) { - let this = ManuallyDrop::new(self); - - unsafe { - // SAFETY: `ManuallyDrop` is transparent to `T`. - // We also aren't dropping `this`, so we can safely - // move out of it. - let this = &*(&raw const this).cast::(); - - 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::(len).unwrap(); - let ptr = alloc(layout).cast::(); - - 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 { - self.into_boxed_slice().into_vec() - } -} - -impl AsMut<[T]> for Vec { - #[inline(always)] - fn as_mut(&mut self) -> &mut [T] { - self.as_mut_slice() - } -} - -impl AsRef<[T]> for Vec { - #[inline(always)] - fn as_ref(&self) -> &[T] { - self.as_slice() - } -} - -impl Borrow<[T]> for Vec { - #[inline(always)] - fn borrow(&self) -> &[T] { - self.as_slice() - } -} - -impl BorrowMut<[T]> for Vec { - #[inline(always)] - fn borrow_mut(&mut self) -> &mut [T] { - self.as_mut_slice() - } -} - -impl Deref for Vec { - type Target = [T]; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - self.as_slice() - } -} - -impl DerefMut for Vec { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut_slice() - } -} - -impl From<[T; N]> for Vec { - #[inline(always)] - fn from(value: [T; N]) -> Self { - unsafe { - let buf = value.as_ptr().cast::<[MaybeUninit; N]>().read(); - - Self { buf, len: N } - } - } -} - -impl TryFrom<&[T]> for Vec { - type Error = LengthError; - - #[inline(always)] - fn try_from(value: &[T]) -> Result { - Self::new(value) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl From> for Box<[T]> { - #[inline(always)] - fn from(value: Vec) -> Self { - value.into_boxed_slice() - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl From> for alloc::vec::Vec { - #[inline(always)] - fn from(value: Vec) -> Self { - value.into_alloc_vec() - } -} diff --git a/oct/src/vec/vec/iter.rs b/oct/src/vec/vec/iter.rs deleted file mode 100644 index b2c0769..0000000 --- a/oct/src/vec/vec/iter.rs +++ /dev/null @@ -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: -// . - -use crate::vec::{IntoIter, Vec}; - -use core::mem::MaybeUninit; -use core::slice; - -impl FromIterator for Vec { - #[inline] - fn from_iter>(iter: I) -> Self { - let mut iter = iter.into_iter(); - - let mut buf = [const { MaybeUninit::::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 IntoIterator for Vec { - type Item = T; - - type IntoIter = IntoIter; - - #[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 { - 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 { - type Item = &'a mut T; - - type IntoIter = slice::IterMut<'a, T>; - - #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} diff --git a/oct/src/vec/vec/mod.rs b/oct/src/vec/vec/mod.rs index 8adf204..9784a73 100644 --- a/oct/src/vec/vec/mod.rs +++ b/oct/src/vec/vec/mod.rs @@ -7,28 +7,28 @@ // . #[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::::try_from([0x3].as_slice()).unwrap(); -/// let vec1 = Vec::::try_from([0x3, 0x2].as_slice()).unwrap(); -/// let vec2 = Vec::::try_from([0x3, 0x2, 0x4].as_slice()).unwrap(); -/// let vec3 = Vec::::try_from([0x3, 0x2, 0x4, 0x3].as_slice()).unwrap(); +/// let v0 = Vec::::try_from([0x3].as_slice()).unwrap(); +/// let v1 = Vec::::try_from([0x3, 0x2].as_slice()).unwrap(); +/// let v2 = Vec::::try_from([0x3, 0x2, 0x4].as_slice()).unwrap(); +/// let v3 = Vec::::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 { - buf: [MaybeUninit; N], len: usize, + buf: [MaybeUninit; N], } impl Vec { @@ -81,18 +81,54 @@ impl Vec { }); } - let mut buf = [const { MaybeUninit::::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::::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; 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 Vec { } } - /// 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 Vec { 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 Vec { /// 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 Vec { 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; N], usize) { + let this = ManuallyDrop::new(self); + + unsafe { + // SAFETY: `ManuallyDrop` is transparent to `T`. + // We also aren't dropping `this`, so we can safely + // move out of it. + let this = &*(&raw const this).cast::(); + + 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::(len).unwrap(); + let ptr = alloc(layout).cast::(); + + 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 { + self.into_boxed_slice().into_vec() + } +} + +impl AsMut<[T]> for Vec { + #[inline(always)] + fn as_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl AsRef<[T]> for Vec { + #[inline(always)] + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl Borrow<[T]> for Vec { + #[inline(always)] + fn borrow(&self) -> &[T] { + self.as_slice() + } +} + +impl BorrowMut<[T]> for Vec { + #[inline(always)] + fn borrow_mut(&mut self) -> &mut [T] { + self.as_mut_slice() } } @@ -261,6 +419,35 @@ impl Debug for Vec { } } +impl Decode for Vec { + type Error = CollectionDecodeError>; + + #[inline] + fn decode(input: &mut decode::Input) -> Result { + let len = Decode::decode(input).unwrap(); + if len > N { + return Err(CollectionDecodeError::BadLength(LengthError { + remaining: N, + count: len, + })); + } + + let mut buf = [const { MaybeUninit::::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 DecodeBorrowed<[T]> for Vec { } + impl Default for Vec { #[inline(always)] fn default() -> Self { @@ -273,6 +460,22 @@ impl Default for Vec { } } +impl Deref for Vec { + type Target = [T]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl DerefMut for Vec { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_slice() + } +} + impl Drop for Vec { #[inline(always)] fn drop(&mut self) { @@ -287,6 +490,47 @@ impl Drop for Vec { } } +impl Encode for Vec { + type Error = <[T] as Encode>::Error; + + #[inline(always)] + fn encode(&self, output: &mut encode::Output) -> Result<(), Self::Error> { + self.as_slice().encode(output) + } +} + +impl Eq for Vec { } + +impl From<[T; N]> for Vec { + #[inline(always)] + fn from(value: [T; N]) -> Self { + unsafe { + let buf = value.as_ptr().cast::<[MaybeUninit; N]>().read(); + + Self { buf, len: N } + } + } +} + +impl FromIterator for Vec { + #[inline] + fn from_iter>(iter: I) -> Self { + let mut iter = iter.into_iter(); + + let mut buf = [const { MaybeUninit::::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 Hash for Vec { #[inline(always)] fn hash(&self, state: &mut H) { @@ -311,3 +555,129 @@ impl, const N: usize> IndexMut for Vec { self.get_mut(index).unwrap() } } + +impl IntoIterator for Vec { + type Item = T; + + type IntoIter = IntoIter; + + #[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 { + 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 { + type Item = &'a mut T; + + type IntoIter = slice::IterMut<'a, T>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl Ord for Vec { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl, U, const N: usize, const M: usize> PartialEq> for Vec { + #[inline(always)] + fn eq(&self, other: &Vec) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl, U, const N: usize, const M: usize> PartialEq<[U; M]> for Vec { + #[inline(always)] + fn eq(&self, other: &[U; M]) -> bool { + self == other.as_slice() + } +} + +impl, U, const N: usize> PartialEq<[U]> for Vec { + #[inline(always)] + fn eq(&self, other: &[U]) -> bool { + self.as_slice() == other + } +} + +impl, U, const N: usize> PartialEq<&[U]> for Vec { + #[inline(always)] + fn eq(&self, other: &&[U]) -> bool { + self == *other + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl, U, const N: usize> PartialEq> for Vec { + #[inline(always)] + fn eq(&self, other: &alloc::vec::Vec) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl PartialOrd> for Vec { + #[inline(always)] + fn partial_cmp(&self, other: &Vec) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl SizedEncode for Vec { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N; +} + +impl TryFrom<&[T]> for Vec { + type Error = LengthError; + + #[inline(always)] + fn try_from(value: &[T]) -> Result { + Self::new(value) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl From> for Box<[T]> { + #[inline(always)] + fn from(value: Vec) -> Self { + value.into_boxed_slice() + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl From> for alloc::vec::Vec { + #[inline(always)] + fn from(value: Vec) -> Self { + value.into_alloc_vec() + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl, U, const N: usize> PartialEq> for alloc::vec::Vec { + #[inline(always)] + fn eq(&self, other: &Vec) -> bool { + self.as_slice() == other.as_slice() + } +} diff --git a/oct/src/vec/vec/tests.rs b/oct/src/vec/vec/test.rs similarity index 100% rename from oct/src/vec/vec/tests.rs rename to oct/src/vec/vec/test.rs