initial version

- preludes

- slice::{RotateInsertExt, SplitOn}, cstr::{UnixCStrAs, CStrSplit}, str::UnixStrAs

- exhaustive testing and checking

- documentation for everything
This commit is contained in:
2026-05-31 00:11:05 +02:00
commit 08d50f8dcc
10 changed files with 330 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
[codespell]
skip = ./target
+1
View File
@@ -0,0 +1 @@
/target
+4
View File
@@ -0,0 +1,4 @@
{
"proseWrap": "always",
"printWidth": 80
}
Generated
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "boilerext"
version = "0.1.0"
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "boilerext"
description = "Rlib with useful boilerplate extension traits for QoL"
version = "0.1.0"
edition = "2024"
license = "MIT"
repository = "https://git.javalsai.tuxcord.net/rust/boilerext"
keywords = ["ext", "trait", "extension-trait"]
categories = ["rust-patterns"]
[features]
default = []
[lints.clippy]
cargo = { level = "warn", priority = -1 }
correctness = { level = "deny", priority = -1 }
nursery = { level = "deny", priority = -1 }
option_if_let_else = { level = "allow" }
pedantic = { level = "deny", priority = -1 }
perf = { level = "deny", priority = -1 }
style = { level = "deny", priority = -1 }
unwrap_used = "deny"
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2026 javalsai
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+32
View File
@@ -0,0 +1,32 @@
# boilerext
Rlib with useful boilerplate extension traits for QoL.
Aims to extend methods that have negligible performance cost, which only
reasonable barrier is their lenthyness or unintuitiveness.
Uses unsafe rust where its safetyness is immediately obvious to squeeze
performance without fear of breaking safety contracts if the abstracted method
wasn't there.
# Docs
Documentation is not hosted publicly for now, clone and run `cargo doc` to
generate it.
# Development
This project uses cargo test/miri test/doc/fmt/check/clippy, codespell, prettier
and shellcheck to check and help in development.
You can check for everything (as long as you have the tools) with
`scripts/check.sh`. For additional stuff pass `extra` as the first argument to
said script.
# License
This code is licensed under the MIT license, a copy can be found at the root of
this project.
The philosophy of the project is just to provide small extension traits with
accessibility ease, so the MIT license was chosen to avoid legal friction.
+1
View File
@@ -0,0 +1 @@
doc-valid-idents = ["QoL", ".."]
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
ANSIRED="\033[1;31m"
ANSIRESET="\033[0m"
errprint() {
printf "%b%s%b\n" \
"$ANSIRED" "$*" "$ANSIRESET" >&2
}
[ -f "Cargo.toml" ] || {
errprint "No Cargo.toml present"
exit 1
}
(
set -x
cargo check -q
cargo clippy -q
cargo test -q
cargo doc -q
cargo fmt --check
codespell
prettier --check .
git ls-files -z "*.sh" | xargs -0 shellcheck
)
if [ "${1:-}" == "extra" ]; then (
set -x
cargo +nightly miri test -q
) fi
+205
View File
@@ -0,0 +1,205 @@
#![doc = include_str!("../README.md")]
pub mod prelude {
//! Prelude with extended method names that should never collide.
//!
//! Trait identifiers are not exported, only the implementation methods are.
//!
//! ```
//! # use std::ffi::CStr;
//! use boilerext::prelude::*;
//! CStr::split_ch;
//! ```
//!
//! ```compile_fail
//! use boilerext::prelude::*;
//! CStrSplit;
//! ```
pub use crate::{cstr::CStrSplit as _, slice::RotateInsertExt as _, slice::SplitOn as _};
}
pub mod unix_prelude {
//! Analogous to [`crate::prelude`] but only for traits exclusive to unix-based systems.
pub use crate::{cstr::UnixCStrAs as _, str::UnixStrAs as _};
}
pub mod slice {
use std::borrow::Borrow;
/// Rotates the slice like `rotate_right` while inserting a given value and returning the
/// popped-off elements.
pub trait RotateInsertExt<T, const N: usize> {
/// Checked version of [`rotate_insert`](Self::rotate_insert)
///
/// ```
/// # use boilerext::slice::RotateInsertExt as _;
/// let array: &mut [u8] = &mut [];
/// assert_eq!(None, array[..].checked_rotate_insert([0]));
/// ```
fn checked_rotate_insert(&mut self, insert: [T; N]) -> Option<[T; N]>;
/// # Panics
///
/// If the slice is smaller than `N`
///
/// # Examples
///
/// ```
/// # use boilerext::slice::RotateInsertExt as _;
/// let array: &mut [u8] = &mut [0, 1, 2, 3, 4];
/// // [ 2.. ];
///
/// // [5, 6] => [2, 3, 4]
/// // [5, 6, 2] => [3, 4]
/// let taken_out = array[2..].rotate_insert([5, 6]);
/// assert_eq!(array, &[0, 1, 5, 6, 2]);
/// assert_eq!(taken_out, [3, 4]);
/// ```
///
/// ```should_panic
/// # use boilerext::slice::RotateInsertExt as _;
/// let array: &mut [u8] = &mut [];
/// let taken_out = array[..].rotate_insert([0]);
/// ```
fn rotate_insert(&mut self, insert: [T; N]) -> [T; N] {
self.checked_rotate_insert(insert)
.expect("slice should be bigger than the rotation")
}
}
impl<T, const N: usize> RotateInsertExt<T, N> for [T] {
fn checked_rotate_insert(&mut self, mut insert: [T; N]) -> Option<[T; N]> {
if self.len() < N {
return None;
}
self.rotate_right(N);
// swaps `insert` with the rotated part of `self` such that `insert` also becomes the
// return slice
for idx in 0..N {
std::mem::swap(&mut self[idx], &mut insert[idx]);
}
Some(insert)
}
}
/// Intended to resemble the currently nightly feature `slice_split_once`
/// ([#112811](https://github.com/rust-lang/rust/issues/112811)) but for a value instead of an
/// equality predicate.
pub trait SplitOn<T: Eq> {
/// # Examples
///
/// ```
/// # use boilerext::slice::SplitOn as _;
/// let (l, r) = b"abcde".split_on(b'c').expect("to contain 'c'");
/// assert_eq!(l, b"ab");
/// assert_eq!(r, b"de");
///
/// let None = b"abcde".split_on(b'0') else { panic!("to not contain '0'"); };
/// ```
fn split_on(&self, value: impl Borrow<T>) -> Option<(&[T], &[T])>;
/// # Examples
///
/// ```
/// # use boilerext::slice::SplitOn as _;
/// let mut slice: [u8; _] = *b"abcde";
/// let (l, r): (&mut _, &mut _) = slice.split_on_mut(b'c').expect("to contain 'c'");
/// assert_eq!(l, b"ab");
/// assert_eq!(r, b"de");
///
/// let None = slice.split_on_mut(b'0') else { panic!("to not contain '0'"); };
/// ```
fn split_on_mut(&mut self, value: impl Borrow<T>) -> Option<(&mut [T], &mut [T])>;
}
impl<T: Eq> SplitOn<T> for [T] {
fn split_on(&self, value: impl Borrow<T>) -> Option<(&[T], &[T])> {
let (idx, _) = self
.iter()
.enumerate()
.find(|(_, b)| *b == value.borrow())?;
// SAFETY: idx is IN-OF-BOUNDS index of the slice
unsafe { Some((self.get_unchecked(0..idx), self.get_unchecked((idx + 1)..))) }
}
fn split_on_mut(&mut self, value: impl Borrow<T>) -> Option<(&mut [T], &mut [T])> {
let (idx, _) = self
.iter()
.enumerate()
.find(|(_, b)| *b == value.borrow())?;
// SAFETY: idx is IN-OF-BOUNDS index of the slice
let (a, b) = unsafe { self.split_at_mut_unchecked(idx) };
Some((a, &mut b[1..])) // b includes index
}
}
}
pub mod cstr {
use std::{
ffi::{CStr, OsStr},
num::NonZeroU8,
};
use crate::slice::SplitOn;
/// Implements transparent `as_*` methods for unix systems.
pub trait UnixCStrAs {
fn as_os_str(&self) -> &OsStr;
}
impl UnixCStrAs for CStr {
fn as_os_str(&self) -> &OsStr {
use std::os::unix::ffi::OsStrExt as _;
OsStr::from_bytes(self.to_bytes())
}
}
/// Implements split methods for [`CStr`]ings
pub trait CStrSplit {
/// # Examples
///
/// ```
/// # use std::num::NonZeroU8;
/// # use boilerext::cstr::CStrSplit;
/// let string = c"abcde";
///
/// let (l, r) = string.split_ch(NonZeroU8::new(b'c').unwrap()).expect("to contain 'c'");
/// assert_eq!(l, b"ab");
/// assert_eq!(r, c"de");
/// ```
fn split_ch(&self, ch: NonZeroU8) -> Option<(&[u8], &CStr)>;
}
impl CStrSplit for CStr {
fn split_ch(&self, ch: NonZeroU8) -> Option<(&[u8], &CStr)> {
let self_slice = self.to_bytes_with_nul();
let (l, r) = self_slice.split_on(ch.get())?;
// SAFETY: split originated in a slice with a final NULL byte.
// Hence, second split, occurring before NULL byte because `ch` can't be 0, will include
// the final byte.
debug_assert!(r.ends_with(&[0]));
Some((l, unsafe { Self::from_bytes_with_nul_unchecked(r) }))
}
}
}
pub mod str {
use std::ffi::OsStr;
/// Implements transparent `as_*` methods for unix systems.
pub trait UnixStrAs {
fn as_os_str(&self) -> &OsStr;
}
impl UnixStrAs for str {
fn as_os_str(&self) -> &OsStr {
use std::os::unix::ffi::OsStrExt as _;
OsStr::from_bytes(self.as_bytes())
}
}
}