Check type parameter at runtime


#1

I have a type that has a type parameter that should, in correct usage, be only one of a subset of types. In particular, [u8; N] where N is a power of two. Since there’s no way to express "must be an array of u8" as a trait bound (and much less that N is a power of two), the type parameter is unbounded. This is an internal type used only by my crate, so the lack of compile-time constraints is fine, but I would still like to debug_assert that the type is being used correctly. Is there any way that I can introspect a type parameter at runtime? What I want is basically something like this:

impl<T> Foo<T> {
    fn new() -> Foo<T> {
        debug_assert!(/* T is [u8; N] where N is a power of two */);
        ...
    }
}

#2

Given that arrays are somewhat clunky right now and support up to 32 elements only (higher than that, and you lose impls), you could simply enumerate all allowable type parameters for your type in a form of a trait.

Π types will solve this, maybe.


#3

As @mhordecki noted, since you only want powers of two, 64 implementations should be more than sufficient for this century :stuck_out_tongue:

trait ValidArray {}

impl ValidArray for [u8; 1] {}
impl ValidArray for [u8; 2] {}
impl ValidArray for [u8; 4] {}
impl ValidArray for [u8; 8] {}
impl ValidArray for [u8; 16] {}
/* ... */

Edit: wrote impl instead of trait.


#4

That’s a great idea, thanks! I assume this is a trait? So:

trait ValidArray {}

impl ValidArray for [u8; 1] {}
impl ValidArray for [u8; 2] {}
...

(in other words, I’m not sure what the impl ValidArray {} in your example was for)

Also, is there a way to prevent people outside the local module from just doing impl ValidArray for String {} or something like that? It doesn’t really matter since this is internal code, so even w/o that guarantee, this is still great for catching accidental errors, but it’d be cool.


#5

If you don’t make it pub, then no one outside the module can see it at all, let alone implement it.


#6

Ah, I don’t think I can because then I’d have a private trait in a public interface. See, e.g., here.


#7

Then you want a “sealed” trait, which is pub but closed to additional implementations. This isn’t a real concept to the compiler yet, but you can approximate it with pub-in-private tricks. Basically, you make implementations use something which is technically pub but lives in a private module, so then only those with access to that private module can really implement it. You can see this trick in rayon here.

Or just make it a pub unsafe trait, and scold anyone who implements it without upholding your specified contract. :slight_smile:


#8

Actually, maybe your ValidArray can just be the pub-in-private piece itself, unless you do need outsiders to be able to name it. So you’d mark it pub but write it in a private module.


#9

You actually have one trait on T that can help you: Sized.

So until pi types make it better, you could do something like

impl<T> Foo<T> {
    fn new() -> Foo<T> {
        assert!(std::mem::size_of::<T>().is_power_of_two());
        assert!(std::mem::align_of::<T>() == 1);
        ...
    }
}

(I upgraded them from debug_assert! since they’re testing compile-time constants, so in release mode LLVM will remove them if they pass anyway, and if they don’t will replace the function body with panic!.)


#10

That worked, cuviper; thanks! Code here.


#11

scottmcm, good call on assert vs debug_assert. I think I’m going to stick with the trait approach since it gives a compile-time guarantee.


#12

Checking at compile time is definitely the way to go :slight_smile: