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?
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 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)
)
}
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.
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.
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.
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!
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.