I aim to create a linear type, i.e., a type that is strictly usable for some integer N number of times.
For example, suppose there is a type named Counter<T, EC, MEC>
where T is any type and both EC and MEC are type-level integers (they can individually be a const generic as well but easier to code examples using typenum::Unsigned
as const-generics-ops is not stabilized) then Counter<T, EC, MEC>
can only be dropped only if EC
= MEC
, or else it should raise a compilation error. Values of type Counter<T, EC, MEC>
can exist transiently as long as they are 'forgotten' with core::mem::forget
or with a similar mechanism and are eventually consumed returning a value of type Counter<T, EC + 1, MEC>
.
The ideal behavior of the program I want is the following:
The following should not be compileable.
use typenum::*;
/** omitted definitions of `Counter`, shown below **/
fn main() {
let counter: Counter<i32, U4, U5> = Counter::new(70);
}
The following should be compileable.
fn main() {
let counter: Counter<i32, U4, U5> = Counter::new(70);
println!("counter EC: {:?}", counter.ec()); // prints 4
// `counter_one` is Counter<i32, U5, U5>`
let counter_one = counter.add_one();
println!("counter_one EC: {:?}", counter_one.ec()); // prints 5
This is because add_one(self)
is a consuming method and within it there is a core::mem::forget(self)
call to tell Rust not to drop the value of counter
.
Here is the full code:
use core::{
marker::PhantomData,
mem::{ManuallyDrop, forget},
ops::{Add, Drop},
};
use typenum::*;
#[repr(transparent)]
struct Counter<T, EC: Unsigned, MEC: Unsigned>(ManuallyDrop<T>, PhantomData<EC>, PhantomData<MEC>);
impl<T, EC, MEC> Counter<T, EC, MEC>
where
EC: Unsigned,
MEC: Unsigned,
{
fn add_one(mut self) -> Counter<T, Sum<EC, U1>, MEC>
where
EC: Unsigned + Add<U1>,
Sum<EC, U1>: Unsigned + Add<U1>,
{
let t = unsafe {ManuallyDrop::<T>::take(&mut self.0)};
const {forget(Self);}
Counter(ManuallyDrop::new(t), PhantomData, PhantomData)
}
const fn new(t: T) -> Self {
Self(ManuallyDrop::new(t), PhantomData, PhantomData)
}
const fn ec(&self) -> usize {
EC::USIZE
}
}
impl<T, EC, MEC> Drop for Counter<T, EC, MEC>
where
EC: Unsigned,
MEC: Unsigned,
{
fn drop(&mut self) {
println!("Drop is called");
struct Check<const CEC: usize, const CMEC: usize>;
const {
if EC::USIZE != MEC::USIZE {
panic!("EC is larger than MEC");
}
};
}
}
fn main() {
let counter: Counter<i32, U4, U5> = Counter::new(70);
println!("counter EC: {:?}", counter.ec());
let counter_one = counter.add_one();
println!("counter_one EC: {:?}", counter_one.ec());
let counter_two = counter_one.add_one();
println!("counter_two EC: {:?}", counter_two.ec());
}
This doesn't not compile as it has two compiler errors:
error[E0080]: evaluation of `<Counter<i32, typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B1>, typenum::B0>, typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B0>, typenum::B1>> as std::ops::Drop>::drop::{constant#0}` failed
--> src/main.rs:46:17
error[E0080]: evaluation of `<Counter<i32, typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B0>, typenum::B0>, typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B0>, typenum::B1>> as std::ops::Drop>::drop::{constant#0}` failed
--> src/main.rs:46:17
This means the panic within the below snippet
const {
if EC::USIZE != MEC::USIZE {
panic!("EC is larger than MEC");
}
};
is triggered for Counter<U4, U5>
and Counter<U6,U5>
.
Also, what this means is Rust will evaluate the const blocks of the Drop
trait implementation for any type that exists within the program.
Question 1
Is there a way to conditionally stop Rust from evaluating the const block within the Drop
trait implementation?
Question 2
In the implementation of add_one
reproduced below:
fn add_one(mut self) -> Counter<T, Sum<EC, U1>, MEC>
where
EC: Unsigned + Add<U1>,
Sum<EC, U1>: Unsigned + Add<U1>,
{
let t = unsafe {ManuallyDrop::<T>::take(&mut self.0)};
const {forget(Self);}
Counter(ManuallyDrop::new(t), PhantomData, PhantomData)
}
The line const {forget(Self);}
actually compiles [Playground]. What on Earth is this forget(Self)
where Self
is the receiver type, not the receiver value!