Infer type in macro?

I've got a function, unsafe_transmute_ref, which converts between references of two types. This is safe because of other traits I'm using (the details aren't important here), but the caller has to verify that the alignment of T is at least as large as the alignment of U in order to guarantee that the return value is properly aligned. I want to provide a macro to do this, but I'm not sure how to do it without requiring the user to manually write out the type parameters. Right now, I've got this:

pub unsafe fn unsafe_transmute_ref<T, U>(x: &T) -> &U
where
    U: FromBits<T>,
{
    &*(x as *const T as *const U)
}

macro_rules! transmute_ref {
    (< $T:ty, $U:ty > $x:expr) => {
        const_assert!(::std::mem::align_of::<$T>() >= ::std::mem::align_of::<$U>());
        unsafe { unsafe_transmute_ref::<$T, $U>($x) }
    };
}

But this requires the user to invoke transmute_ref! as a cumbersome transmute_ref!(<T, U> my_ref). I'd prefer if the macro could instead just be invoked as transmute_ref!(my_ref), and have the type inference capabilities of Rust automatically figure out what type parameters to use for the two calls to mem::align_of inside the macro. Is there any way to structure things so that Rust can infer the type parameters for mem::align_of so the user doesn't have to pass the types manually?

Why don't you check the alignments inside unsafe_transmute_ref where you know T and U?

Because then I'd have to panic at runtime. This way, I can use const_assert!, which succeeds or fails at compile time.

The problem is that macros are expanded before types exist, so you can't infer types in macros. You can't use align_of_val in a constant context, but you can't use align_of without being able to name the type, which you can't do, because you don't have names, Rust doesn't have decltype or typeof, and you can't use them inside a generic function because Rust won't propagate outer generic parameters.

I don't think you can do this.

I don't think it's that impossible. What I'm asking for may end up being impossible, but there are cases in which it's doable. Consider, for example:

const fn align_of<T>(_: &T) -> usize {
    ::std::mem::size_of::<T>()
}

// fails to compile if the types of $x and $y don't have the same alignment
macro_rules! assert_eq_align {
    ($x:expr, $y:expr) => (
        // don't actually evaluate at runtime
        if false { const_assert!(align_of(&$x) == align_of(&$y)); }
        ($x, $y)
    )
}

This actually works.

The issue that I'm having in my case is that, since unsafe_transmute_ref isn't itself const, I can't figure out a way to get the output type.

It can't be both those things.

Because you're not using static_assert! in align_of. You can't use static_assert! inside a function to give T and U names. You can't give inferred types names in a way that lets you act on them outside of a function. Every method I can think of either requires you to name the type, or won't work in a constant context.

Have you considered using a test instead?

Well that's the point of the const fn align_of that takes a &T argument - you can give T a name inside the function body, but since the function is const, it allows you to treat the return value (the alignment) as a constant. What I'm trying to do, then, is to have an expression of the form align_of(&x) where Rust infers the type of x to be equal to the return type of unsafe_transmute_ref.

I already covered that. It's called align_of_val, and it doesn't work with static_assert!.

Here's a hacky workaround:

trait AlignCheck {
    const BAD: u8;
}
impl<T, U> AlignCheck for (T, U) {
    // This is a division by 0 if alignof(T) < alignof(U),
    // producing a constant evaluation error:
    const BAD: u8 = 1u8 / ((std::mem::align_of::<T>() < std::mem::align_of::<U>()) as u8);
}

pub unsafe fn unsafe_transmute_ref<T, U>(x: &T) -> &U
{
    let _ = <(T, U) as AlignCheck>::BAD;
    &*(x as *const T as *const U)
}

(Once const generics are implemented, or even if this bug is fixed it'll be possible to actually make it a trait bound. Someday…)

6 Likes

There's actually a difference because align_of_val isn't const, while my bespoke align_of is. They're obviously essentially the same, but I need const for this, so it makes a difference.

God I wish that worked. Unfortunately it doesn't seem to. Maybe I'm missing something?

I think it's just a matter of changing:

let _ = <(T, U) as AlignCheck>::BAD;

to

let _ = <(U, T) as AlignCheck>::BAD;

Right you are. Awesome!

That's evil. Well done.

4 Likes

Do you know if there's a way to get this to fail if you merely specify a trait bound? In particular, I'd like the following to work:

trait FitsIn<T> {
    const BAD: u8;
}

impl<T, U> FitsIn<T> for U {
    const BAD: u8 = 1u8 / ((std::mem::size_of::<T>() >= std::mem::size_of::<U>()) as u8);
}

unsafe fn transmute<T, U: FitsIn<T>>(_t: T) -> U {
    unimplemented!()
}

However, this doesn't work. I have to add the line you had:

unsafe fn transmute<T, U: FitsIn<T>>(_t: T) -> U {
    let _ = <U as FitsIn<T>>::BAD;
    unimplemented!()
}

(playground link)

This concerns me because you could imagine somebody in an outer API layer with a U: FitsIn<T> bound that doesn't know they need that line creating code that compiles fine until they actually call transmute (or some other code that knows to use the magic let binding).

Not sure how you force associated const eval without using the const. But I think the idea behind this trait was not to use it as a general purpose trait, but only as a “carrier” of this associated const and trigger its eval.

Another approach, which has no compile-time alignment checking however, is to make FitsIn<T> an API-level trait, mark it unsafe, and then force callers to implement it for the (T, U) types they intend to use. You can impl it for known types (eg integers) and users can impl for their own types with whatever enforcement/testing they want to ensure the guarantee stays upheld.

It’d be cool if one could implement traits based on a const-eval condition! So something like:

impl<T, U> FitsIn<U> for T where std::mem::align_of::<T> >= std::mem::align_of::<U>() {}

But maybe there’s a way to make the current hack ... even hackier! :slight_smile:

Out of curiosity, how would that work? Particularly in the case of the bug being fixed, since that will likely happen sooner than const generics.

Perhaps something like:

trait FitsIn<T> where Self: Sized, T: Sized {
    const BAD: [u8; 1 / ((std::mem::size_of::<T>() >= std::mem::size_of::<Self>()) as usize)];
}

This should trigger the const eval to figure out the type of BAD, whereas the existing versions discussed in this thread were relying on triggering const eval to get the value of a particular impl.

Ooooh cool.