#![doc = include_str!("../tests/i32-error-message.expected")]
#![doc = include_str!("../tests/2.5k-error-message.expected")]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{
alloc::{alloc, handle_alloc_error, Layout},
boxed::Box,
};
use core::{any, fmt, mem::MaybeUninit, ptr::NonNull};
#[inline(always)]
pub fn new<T>(x: T) -> Result<Box<T>, ErrorWith<T>> {
match imp(x) {
Ok(it) => Ok(it),
Err(e) => Err(ErrorWith(e)),
}
}
#[inline(always)]
pub fn or_drop<T>(x: T) -> Result<Box<T>, Error> {
match new(x) {
Ok(it) => Ok(it),
Err(e) => Err(e.without_payload()),
}
}
#[inline(always)]
fn imp<T>(x: T) -> Result<Box<T>, T> {
let layout = Layout::for_value(&x);
match layout.size() == 0 {
true => {
let ptr = NonNull::<T>::dangling().as_ptr();
Ok(unsafe { Box::from_raw(ptr) })
}
false => {
let ptr = unsafe { alloc(layout) }.cast::<T>();
match ptr.is_null() {
true => Err(x),
false => {
let mut heap = unsafe { Box::<MaybeUninit<T>>::from_raw(ptr.cast()) };
heap.write(x);
Ok(unsafe { Box::from_raw(Box::into_raw(heap).cast()) })
}
}
}
}
}
pub struct Error {
info: fn() -> Info,
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Info { layout, name } = self.info();
let mut d = f.debug_struct("Error");
d.field("layout", &layout).field("name", &name);
d.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_info(self.info(), f)
}
}
fn write_info(info: Info, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Info { layout, name } = info;
let mut size = layout.size() as f64;
let mut prefix = "";
let boundary = 1024.0;
for next in [
"kibi", "mebi", "gibi", "tebi", "pebi", "exbi", "zebi", "yobi",
] {
if size <= boundary {
break;
}
size /= boundary;
prefix = next;
}
let precision = match fract(size) == 0.0 {
true => 0,
false => 2,
};
f.write_fmt(format_args!(
"memory allocation of {size:.precision$} {prefix}bytes (for type {name}) failed",
))
}
const fn fract(x: f64) -> f64 {
x - trunc(x)
}
const fn trunc(x: f64) -> f64 {
let mut i: u64 = x.to_bits();
let e: i64 = (i >> 52 & 0x7ff) as i64 - 0x3ff + 12;
let m: u64 = -1i64 as u64 >> e;
if e >= 52 + 12 {
return x;
}
if (i & m) == 0 {
return x;
}
i &= !m;
f64::from_bits(i)
}
#[cfg(not(feature = "std"))]
impl core::error::Error for Error {}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl Error {
#[inline(always)]
fn info(&self) -> Info {
(self.info)()
}
#[inline(always)]
pub fn handle(self) -> ! {
handle_alloc_error(self.layout())
}
#[inline(always)]
pub fn layout(&self) -> Layout {
self.info().layout
}
}
#[cfg(feature = "std")]
impl From<Error> for std::io::Error {
fn from(value: Error) -> Self {
let kind = std::io::ErrorKind::OutOfMemory;
match or_drop(value) {
Ok(source) => {
std::io::Error::new(kind, source as Box<dyn std::error::Error + Send + Sync>)
}
Err(_cannot_preserve) => std::io::Error::from(kind),
}
}
}
#[cfg(feature = "std")]
impl From<Error> for std::io::ErrorKind {
fn from(_: Error) -> Self {
std::io::ErrorKind::OutOfMemory
}
}
trait Indirect: Sized {
fn info() -> Info {
Info {
layout: Layout::new::<Self>(),
name: any::type_name::<Self>(),
}
}
}
impl<T: Sized> Indirect for T {}
#[derive(Debug, Clone, Copy)]
struct Info {
layout: Layout,
name: &'static str,
}
#[derive(Debug)]
pub struct ErrorWith<T>(pub T);
impl<T> ErrorWith<T> {
fn info(&self) -> Info {
Info {
layout: Layout::for_value(&self.0),
name: any::type_name::<T>(),
}
}
pub fn without_payload(self) -> Error {
Error { info: T::info }
}
}
impl<T> fmt::Display for ErrorWith<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_info(self.info(), f)
}
}
#[cfg(not(feature = "std"))]
impl<T: fmt::Debug> core::error::Error for ErrorWith<T> {}
#[cfg(feature = "std")]
impl<T: fmt::Debug> std::error::Error for ErrorWith<T> {}
impl<T> From<ErrorWith<T>> for Error {
fn from(value: ErrorWith<T>) -> Self {
value.without_payload()
}
}
#[cfg(feature = "std")]
impl<T> From<ErrorWith<T>> for std::io::Error {
fn from(value: ErrorWith<T>) -> Self {
Error::from(value).into()
}
}
#[cfg(feature = "std")]
impl<T> From<ErrorWith<T>> for std::io::ErrorKind {
fn from(_: ErrorWith<T>) -> Self {
std::io::ErrorKind::OutOfMemory
}
}
#[cfg(test)]
mod tests {
use super::*;
static_assertions::assert_eq_size!(Error, *const u8);
static_assertions::assert_impl_all!(Error: Send, Sync);
}