microsd 0.2.0

Light‐weight systemd auxiliars
Documentation
use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use std::ffi::CStr;
use std::fmt;
use std::ptr::{self, NonNull};
use std::sync::atomic::AtomicPtr;

use crate::atomic;

enum ThinStrRaw {}
pub struct ThinStr(NonNull<ThinStrRaw>);
pub struct AtomicThinStr(AtomicPtr<ThinStrRaw>);

impl ThinStr {
	fn layout(size: usize) -> (Layout, usize) {
		Layout::array::<u8>(size)
			.and_then(|layout| Layout::new::<usize>().extend(layout))
			.unwrap_or_else(|_| handle_alloc_error(Layout::new::<()>()))
	}

	pub fn new(src: &(impl ?Sized + AsRef<str>)) -> Self {
		let slice = src.as_ref().as_bytes();
		let (layout, offset) = Self::layout(slice.len());
		let ptr = unsafe { alloc(layout) };
		if ptr.is_null() {
			handle_alloc_error(layout);
		}

		unsafe { ptr.cast::<usize>().write(slice.len()) };
		unsafe {
			ptr.byte_add(offset).copy_from_nonoverlapping(slice.as_ptr(), slice.len());
		};

		Self(unsafe { NonNull::new_unchecked(ptr.cast()) })
	}

	#[inline]
	pub fn from_cstr(cstr: &(impl ?Sized + AsRef<CStr>)) -> Option<Self> {
		cstr.as_ref().to_str().ok().map(Self::new)
	}

	#[inline]
	pub fn len(&self) -> usize {
		unsafe { self.0.cast::<usize>().read() }
	}

	#[inline]
	pub fn as_ptr(&self) -> NonNull<[u8]> {
		let size = self.len();
		unsafe {
			NonNull::slice_from_raw_parts(self.0.byte_add(Self::layout(size).1).cast(), size)
		}
	}

	#[inline]
	pub fn as_bytes(&self) -> &[u8] {
		unsafe { self.as_ptr().as_ref() }
	}

	#[inline]
	pub fn as_str(&self) -> &str {
		unsafe { str::from_utf8_unchecked(self.as_bytes()) }
	}

	#[inline]
	fn into_raw(self) -> *mut ThinStrRaw {
		let ptr = self.0.as_ptr().cast();
		std::mem::forget(self);
		ptr
	}

	#[inline]
	unsafe fn from_raw(raw: *mut ThinStrRaw) -> Option<Self> {
		NonNull::new(raw.cast()).map(Self)
	}
}

impl Drop for ThinStr {
	fn drop(&mut self) {
		unsafe { dealloc(self.0.as_ptr().cast(), Self::layout(self.len()).0); }
	}
}

impl fmt::Debug for ThinStr {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_str().fmt(f) }
}

impl AsRef<str> for ThinStr {
	#[inline]
	fn as_ref(&self) -> &str { self.as_str() }
}

impl AtomicThinStr {
	pub const fn new() -> Self {
		Self(AtomicPtr::new(ptr::null_mut()))
	}

	pub fn load(&self) -> Option<ThinStr> {
		unsafe { ThinStr::from_raw(atomic::load!(self.0)) }
	}

	pub fn store(&self, thin: ThinStr) {
		atomic::store!(self.0, thin.into_raw());
	}
}

#[cfg(test)]
mod tests {
	use super::ThinStr;

	#[test]
	fn eq() {
		let wide = "foo";
		let thin = ThinStr::new(wide);

		assert_eq!(wide.len(), thin.len());
		assert_eq!(wide, thin.as_str());
	}

	#[test]
	fn restore() {
		let wide = "bar";
		let thin = unsafe {
			ThinStr::from_raw(ThinStr::new(wide).into_raw()).unwrap()
		};

		assert_eq!(wide, thin.as_str());
	}
}