mirror of
https://github.com/Noratrieb/stuff.git
synced 2026-01-14 16:35:08 +01:00
test all backends!
This commit is contained in:
parent
5164f70545
commit
14e7443532
6 changed files with 374 additions and 234 deletions
3
.rustfmt.toml
Normal file
3
.rustfmt.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
imports_granularity = "Crate"
|
||||
newline_style = "Unix"
|
||||
group_imports = "StdExternalCrate"
|
||||
|
|
@ -7,3 +7,6 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
sptr = "0.2.3"
|
||||
|
||||
[dev-dependencies]
|
||||
paste = "1.0.7"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,29 @@
|
|||
use sptr::Strict;
|
||||
use std::mem;
|
||||
|
||||
use sptr::Strict;
|
||||
|
||||
/// A backend where the stuffed pointer is stored. Must be bigger or equal to the pointer size.
|
||||
pub trait Backend<T> {
|
||||
/// The underlying type where the data is stored. Often a tuple of a pointer (for the provenance)
|
||||
/// and some integers to fill up the bytes.
|
||||
type Stored: Copy;
|
||||
|
||||
/// Get the pointer from the backed. Since the [`crate::StuffingStrategy`] is able to use the full
|
||||
/// bytes to pack in the pointer address, the full address is returned in the second tuple field,
|
||||
/// as the integer. The provenance of the pointer is returned as the first tuple field, but its
|
||||
/// address should be ignored and may be invalid.
|
||||
fn get_ptr(s: Self::Stored) -> (*mut T, Self);
|
||||
|
||||
/// Set a new pointer address. The provenance of the new pointer is transferred in the first argument,
|
||||
/// and the address in the second. See [`Backend::get_ptr`] for more details on the separation.
|
||||
fn set_ptr(provenance: *mut T, addr: Self) -> Self::Stored;
|
||||
|
||||
/// Get the integer value from the backend. Note that this *must not* be used to create a pointer,
|
||||
/// for that use [`Backend::get_ptr`] to keep the provenance.
|
||||
fn get_int(s: Self::Stored) -> Self;
|
||||
}
|
||||
|
||||
#[allow(clippy::should_assert_eq, dead_code)] // :/
|
||||
#[allow(dead_code)] // :/
|
||||
const fn assert_size<B>()
|
||||
where
|
||||
B: Backend<()>,
|
||||
|
|
|
|||
136
src/lib.rs
136
src/lib.rs
|
|
@ -3,18 +3,28 @@
|
|||
#![warn(missing_docs)]
|
||||
|
||||
//! A crate for stuffing things into a pointer.
|
||||
//!
|
||||
//! This crate consists of three parts:
|
||||
//! * The type [`StuffedPtr`]
|
||||
//! * The trait [`StuffingStrategy`]
|
||||
//! * The trait [`Backend`]
|
||||
//!
|
||||
//!
|
||||
|
||||
mod backend;
|
||||
pub mod strategies;
|
||||
mod strategy;
|
||||
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::ops::Not;
|
||||
use std::{
|
||||
fmt::{Debug, Formatter},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
ops::Not,
|
||||
};
|
||||
|
||||
use crate::backend::Backend;
|
||||
use sptr::Strict;
|
||||
|
||||
pub use crate::{backend::Backend, strategy::StuffingStrategy};
|
||||
|
||||
/// A union of a pointer and some extra data.
|
||||
pub struct StuffedPtr<T, S, I = usize>(I::Stored, PhantomData<S>)
|
||||
where
|
||||
|
|
@ -235,142 +245,115 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A trait that describes how to stuff extras and pointers into the pointer sized object.
|
||||
/// # Safety
|
||||
///
|
||||
/// If [`StuffingStrategy::is_extra`] returns true for a value, then
|
||||
/// [`StuffingStrategy::extract_extra`] *must* return a valid `Extra` for that same value.
|
||||
///
|
||||
/// [`StuffingStrategy::stuff_extra`] *must* consume `inner` and make sure that it's not dropped
|
||||
/// if it isn't `Copy`.
|
||||
///
|
||||
/// For [`StuffingStrategy::stuff_ptr`] and [`StuffingStrategy::extract_ptr`],
|
||||
/// `ptr == extract_ptr(stuff_ptr(ptr))` *must* hold true.
|
||||
pub unsafe trait StuffingStrategy<I> {
|
||||
/// The type of the extra.
|
||||
type Extra;
|
||||
|
||||
/// Checks whether the `StufferPtr` data value contains an extra value. The result of this
|
||||
/// function can be trusted.
|
||||
fn is_extra(data: I) -> bool;
|
||||
|
||||
/// Stuff extra data into a usize that is then put into the pointer. This operation
|
||||
/// must be infallible.
|
||||
fn stuff_extra(inner: Self::Extra) -> I;
|
||||
|
||||
/// Extract extra data from the data.
|
||||
/// # Safety
|
||||
/// `data` must contain data created by [`StuffingStrategy::stuff_extra`].
|
||||
unsafe fn extract_extra(data: I) -> Self::Extra;
|
||||
|
||||
/// Stuff a pointer address into the pointer sized integer.
|
||||
///
|
||||
/// This can be used to optimize away some of the unnecessary parts of the pointer or do other
|
||||
/// cursed things with it.
|
||||
///
|
||||
/// The default implementation just returns the address directly.
|
||||
fn stuff_ptr(addr: usize) -> I;
|
||||
|
||||
/// Extract the pointer address from the data.
|
||||
///
|
||||
/// This function expects `inner` to come directly from [`StuffingStrategy::stuff_ptr`].
|
||||
fn extract_ptr(inner: I) -> usize;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::strategies::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop};
|
||||
use crate::StuffedPtr;
|
||||
#![allow(non_snake_case)]
|
||||
use std::mem;
|
||||
|
||||
// note: the tests mostly use the `PanicsInDrop` type and strategy, to make sure that no
|
||||
use paste::paste;
|
||||
|
||||
use crate::{
|
||||
strategy::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop},
|
||||
StuffedPtr,
|
||||
};
|
||||
|
||||
// extra is ever dropped accidentally.
|
||||
|
||||
// note: the tests mostly use the `PanicsInDrop` type and strategy, to make sure that no
|
||||
macro_rules! make_tests {
|
||||
($backend:ident) => {
|
||||
paste! {
|
||||
#[test]
|
||||
fn set_get_ptr_no_extra() {
|
||||
fn [<set_get_ptr_no_extra__ $backend>]() {
|
||||
unsafe {
|
||||
let boxed = Box::new(1);
|
||||
let stuffed_ptr: StuffedPtr<i32, ()> = boxed.into();
|
||||
let stuffed_ptr: StuffedPtr<i32, (), $backend> = boxed.into();
|
||||
let ptr = stuffed_ptr.get_ptr_unchecked();
|
||||
let boxed = Box::from_raw(ptr);
|
||||
assert_eq!(*boxed, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn get_extra() {
|
||||
let stuffed_ptr: StuffedPtr<(), EmptyInMax> = StuffedPtr::new_extra(EmptyInMax);
|
||||
fn [<get_extra__ $backend>]() {
|
||||
let stuffed_ptr: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_extra(EmptyInMax);
|
||||
assert!(stuffed_ptr.is_extra());
|
||||
assert!(matches!(stuffed_ptr.copy_extra(), Some(EmptyInMax)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() {
|
||||
fn [<debug__ $backend>]() {
|
||||
let boxed = Box::new(1);
|
||||
let stuffed_ptr: StuffedPtr<i32, HasDebug> = boxed.into();
|
||||
let stuffed_ptr: StuffedPtr<i32, HasDebug, $backend> = boxed.into();
|
||||
assert!(format!("{stuffed_ptr:?}").starts_with("StuffedPtr::Ptr {"));
|
||||
|
||||
drop(unsafe { Box::from_raw(stuffed_ptr.get_ptr().unwrap()) });
|
||||
|
||||
let extra = HasDebug;
|
||||
let stuffed_ptr: StuffedPtr<i32, HasDebug> = StuffedPtr::new_extra(extra);
|
||||
let stuffed_ptr: StuffedPtr<i32, HasDebug, $backend> = StuffedPtr::new_extra(extra);
|
||||
assert_eq!(
|
||||
format!("{stuffed_ptr:?}"),
|
||||
"StuffedPtr::Extra { extra: hello! }"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn drop_extra_when_extra() {
|
||||
let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
fn [<drop_extra_when_extra__ $backend>]() {
|
||||
let stuffed_ptr: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
// the panicking drop needs to be called here!
|
||||
drop(stuffed_ptr);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn clone() {
|
||||
fn [<clone__ $backend>]() {
|
||||
let mut unit = ();
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
|
||||
let _ = stuffed_ptr1.clone();
|
||||
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
let stuffed_ptr2 = stuffed_ptr1.clone();
|
||||
|
||||
mem::forget((stuffed_ptr1, stuffed_ptr2));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn eq() {
|
||||
fn [<eq__ $backend>]() {
|
||||
// two pointers
|
||||
let mut unit = ();
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
|
||||
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
|
||||
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
|
||||
|
||||
assert_eq!(stuffed_ptr1, stuffed_ptr2);
|
||||
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
|
||||
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
|
||||
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
|
||||
assert_ne!(stuffed_ptr1, stuffed_ptr2);
|
||||
mem::forget(stuffed_ptr2);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn dont_drop_extra_when_pointer() {
|
||||
fn [<dont_drop_extra_when_pointer__ $backend>]() {
|
||||
let mut unit = ();
|
||||
let stuffed_ptr: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_ptr(&mut unit);
|
||||
let stuffed_ptr: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit);
|
||||
// the panicking drop needs not to be called here!
|
||||
drop(stuffed_ptr);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn some_traits_dont_drop() {
|
||||
fn [<some_traits_dont_drop__ $backend>]() {
|
||||
// make sure that extra is never dropped twice
|
||||
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_extra(PanicsInDrop);
|
||||
|
||||
// PartialEq
|
||||
assert_eq!(stuffed_ptr1, stuffed_ptr2);
|
||||
|
|
@ -379,4 +362,11 @@ mod tests {
|
|||
|
||||
mem::forget((stuffed_ptr1, stuffed_ptr2));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
make_tests!(u128);
|
||||
make_tests!(u64);
|
||||
make_tests!(usize);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
//! Several pre-defined strategies to use with `StuffedPtr`.
|
||||
//!
|
||||
//! * `()`: An empty strategy, is always the pointer
|
||||
|
||||
use crate::StuffingStrategy;
|
||||
|
||||
unsafe impl StuffingStrategy<usize> for () {
|
||||
type Extra = ();
|
||||
|
||||
fn is_extra(_data: usize) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn stuff_extra(_inner: Self::Extra) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: usize) -> Self::Extra {}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> usize {
|
||||
addr
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: usize) -> usize {
|
||||
inner
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_strategies {
|
||||
use crate::StuffingStrategy;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
macro_rules! impl_usize_max_zst {
|
||||
($ty:ident) => {
|
||||
// this one lives in usize::MAX
|
||||
unsafe impl StuffingStrategy<usize> for $ty {
|
||||
type Extra = Self;
|
||||
|
||||
fn is_extra(data: usize) -> bool {
|
||||
data == usize::MAX
|
||||
}
|
||||
|
||||
#[allow(clippy::forget_copy)]
|
||||
fn stuff_extra(inner: Self::Extra) -> usize {
|
||||
std::mem::forget(inner);
|
||||
usize::MAX
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: usize) -> Self::Extra {
|
||||
$ty
|
||||
}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> usize {
|
||||
addr
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: usize) -> usize {
|
||||
inner
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EmptyInMax;
|
||||
|
||||
impl_usize_max_zst!(EmptyInMax);
|
||||
|
||||
pub struct HasDebug;
|
||||
|
||||
impl Debug for HasDebug {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("hello!")
|
||||
}
|
||||
}
|
||||
|
||||
impl_usize_max_zst!(HasDebug);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PanicsInDrop;
|
||||
|
||||
impl Drop for PanicsInDrop {
|
||||
fn drop(&mut self) {
|
||||
panic!("oh no!!!");
|
||||
}
|
||||
}
|
||||
|
||||
impl_usize_max_zst!(PanicsInDrop);
|
||||
}
|
||||
222
src/strategy.rs
Normal file
222
src/strategy.rs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/// A trait that describes how to stuff extras and pointers into the pointer sized object.
|
||||
/// # Safety
|
||||
///
|
||||
/// If [`StuffingStrategy::is_extra`] returns true for a value, then
|
||||
/// [`StuffingStrategy::extract_extra`] *must* return a valid `Extra` for that same value.
|
||||
///
|
||||
/// [`StuffingStrategy::stuff_extra`] *must* consume `inner` and make sure that it's not dropped
|
||||
/// if it isn't `Copy`.
|
||||
///
|
||||
/// For [`StuffingStrategy::stuff_ptr`] and [`StuffingStrategy::extract_ptr`],
|
||||
/// `ptr == extract_ptr(stuff_ptr(ptr))` *must* hold true.
|
||||
pub unsafe trait StuffingStrategy<I> {
|
||||
/// The type of the extra.
|
||||
type Extra;
|
||||
|
||||
/// Checks whether the `StufferPtr` data value contains an extra value. The result of this
|
||||
/// function can be trusted.
|
||||
fn is_extra(data: I) -> bool;
|
||||
|
||||
/// Stuff extra data into a usize that is then put into the pointer. This operation
|
||||
/// must be infallible.
|
||||
fn stuff_extra(inner: Self::Extra) -> I;
|
||||
|
||||
/// Extract extra data from the data.
|
||||
/// # Safety
|
||||
/// `data` must contain data created by [`StuffingStrategy::stuff_extra`].
|
||||
unsafe fn extract_extra(data: I) -> Self::Extra;
|
||||
|
||||
/// Stuff a pointer address into the pointer sized integer.
|
||||
///
|
||||
/// This can be used to optimize away some of the unnecessary parts of the pointer or do other
|
||||
/// cursed things with it.
|
||||
///
|
||||
/// The default implementation just returns the address directly.
|
||||
fn stuff_ptr(addr: usize) -> I;
|
||||
|
||||
/// Extract the pointer address from the data.
|
||||
///
|
||||
/// This function expects `inner` to come directly from [`StuffingStrategy::stuff_ptr`].
|
||||
fn extract_ptr(inner: I) -> usize;
|
||||
}
|
||||
|
||||
unsafe impl StuffingStrategy<usize> for () {
|
||||
type Extra = ();
|
||||
|
||||
fn is_extra(_data: usize) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn stuff_extra(_inner: Self::Extra) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: usize) -> Self::Extra {}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> usize {
|
||||
addr
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: usize) -> usize {
|
||||
inner
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl StuffingStrategy<u64> for () {
|
||||
type Extra = ();
|
||||
|
||||
fn is_extra(_data: u64) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn stuff_extra(_inner: Self::Extra) -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: u64) -> Self::Extra {}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> u64 {
|
||||
addr as u64
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: u64) -> usize {
|
||||
inner as usize
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl StuffingStrategy<u128> for () {
|
||||
type Extra = ();
|
||||
|
||||
fn is_extra(_data: u128) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn stuff_extra(_inner: Self::Extra) -> u128 {
|
||||
0
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: u128) -> Self::Extra {}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> u128 {
|
||||
addr as u128
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: u128) -> usize {
|
||||
inner as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_strategies {
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
use super::StuffingStrategy;
|
||||
|
||||
macro_rules! impl_usize_max_zst {
|
||||
($ty:ident) => {
|
||||
// this one lives in usize::MAX
|
||||
unsafe impl StuffingStrategy<usize> for $ty {
|
||||
type Extra = Self;
|
||||
|
||||
fn is_extra(data: usize) -> bool {
|
||||
data == usize::MAX
|
||||
}
|
||||
|
||||
#[allow(clippy::forget_copy)]
|
||||
fn stuff_extra(inner: Self::Extra) -> usize {
|
||||
std::mem::forget(inner);
|
||||
usize::MAX
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: usize) -> Self::Extra {
|
||||
$ty
|
||||
}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> usize {
|
||||
addr
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: usize) -> usize {
|
||||
inner
|
||||
}
|
||||
}
|
||||
unsafe impl StuffingStrategy<u64> for $ty {
|
||||
type Extra = Self;
|
||||
|
||||
fn is_extra(data: u64) -> bool {
|
||||
data == u64::MAX
|
||||
}
|
||||
|
||||
#[allow(clippy::forget_copy)]
|
||||
fn stuff_extra(inner: Self::Extra) -> u64 {
|
||||
std::mem::forget(inner);
|
||||
u64::MAX
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: u64) -> Self::Extra {
|
||||
$ty
|
||||
}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> u64 {
|
||||
addr as u64
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: u64) -> usize {
|
||||
inner as usize
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl StuffingStrategy<u128> for $ty {
|
||||
type Extra = Self;
|
||||
|
||||
fn is_extra(data: u128) -> bool {
|
||||
data == u128::MAX
|
||||
}
|
||||
|
||||
#[allow(clippy::forget_copy)]
|
||||
fn stuff_extra(inner: Self::Extra) -> u128 {
|
||||
std::mem::forget(inner);
|
||||
u128::MAX
|
||||
}
|
||||
|
||||
unsafe fn extract_extra(_data: u128) -> Self::Extra {
|
||||
$ty
|
||||
}
|
||||
|
||||
fn stuff_ptr(addr: usize) -> u128 {
|
||||
addr as u128
|
||||
}
|
||||
|
||||
fn extract_ptr(inner: u128) -> usize {
|
||||
inner as usize
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EmptyInMax;
|
||||
|
||||
impl_usize_max_zst!(EmptyInMax);
|
||||
|
||||
pub struct HasDebug;
|
||||
|
||||
impl Debug for HasDebug {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("hello!")
|
||||
}
|
||||
}
|
||||
|
||||
impl_usize_max_zst!(HasDebug);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PanicsInDrop;
|
||||
|
||||
impl Drop for PanicsInDrop {
|
||||
fn drop(&mut self) {
|
||||
panic!("oh no!!!");
|
||||
}
|
||||
}
|
||||
|
||||
impl_usize_max_zst!(PanicsInDrop);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue