Any way to use const member of non const value's type in a const context?

Been pulling my hair trying to figure this one out for some time now.

Say I have this:

trait Foo {
    const MEMBER: usize;
    fn get_member(&self) -> usize { Self::MEMBER }
}

Given x: TX with TX: Foo, I can get MEMBER either by doing TX::MEMBER or x.get_member(). This is good for non-const contexts.

Say I have a macro (or, really, any context where I can only get an opaque value as-is) that takes a value of a type implementing Foo. I want to perform a static check on the value of MEMBER:

macro_rules! mac {
    ($e:expr) => {
        const CHECK: () = assert!($e::MEMBER != 0); // sic
    }
}

The above doesn’t work, because :: must be used on a type. I can’t use get_member (even if it were a const fn) either because then I’d get error[E0435]: attempt to use a non-constant value in a constant.

I’ve thought about inference tricks but they all end up “using” $e in a way that counts as E0435.

If Rust had C’s typeof (which is const even for non-const operands, since they’re unevaluated) I could do typeof($e)::MEMBER, but it doesn’t.

Is there any way, in Rust (assume latest nightly, any unstable feature you want) to do this? To recap: getting a const member of the type of a non-const value in a const context.

Well, like the prophecy said, I found the answer about 30 seconds before posting this, even though I’ve been looking for an answer for days. So this is an auto-answer.

TAIT is the way:

type TypeCheck = impl Foo;
let _: &TypeCheck = &x;
const MEMBER: usize = TypeCheck::MEMBER;

And this is what I’ve come up with so process multiple items at once (e.g. a tuple), using #![feature(macro_metavar_expr_concat)]:

macro_rules! check_sum {
    ($prefix:expr, [$id:ident $(, $rid:ident)*], $head:expr $(, $rest:expr)*) => {
        type ${ concat(T, $id) } = impl Foo;
        let _: &${ concat(T, $id) } = &$head;
        const $id: usize = ${ concat(T, $id) }::MEMBER;
        check_sum!($prefix, [${ concat(I, $id) }, $id $(, $rid)*], $($rest),*);
    };
    ($prefix:expr, [$_:ident $(,$id:ident)*], ) => {
        const TOTAL: usize = ($prefix as usize) $(+ $id)*;
        const CHECK: () = assert!(TOTAL <= 16, "Sum exceeds 16");
    };
    ($prefix:expr, $($lst:expr),*) => {
        check_sum!($prefix, [I], $($lst),*);
    }
}

You don’t need TAIT or any unstable features. You can use an “inference trick” that doesn’t attempt to actually const evaluate the expression (unless the macro is itself called from a const context). The key is to call a generic function (which can therefore have a name T for the type and refer to T::MEMBER) in a way which could evaluate the expression, but in fact never does.

pub const fn assert_helper<T: Foo>(_: [T; 0]) {
    const {
        assert!(T::MEMBER != 0);
    }
}

macro_rules! mac {
    ($e:expr) => {
        $crate::assert_helper(if false { [$e; 0] } else { [] })
    };
}
1 Like

Wow, huh. So there was a way to reference a non-const value without evaluating it, neat. Thanks!

Adapted to my list-of-things use-case, it gives:

macro_rules! check_sum {
    ([$id:ident $(, $rid:ident => $rval:expr)*], $head:expr $(, $rest:expr)*) => {
        check_sum!([${ concat(i, $id) }, $id => $head $(, $rid => $rval)*], $($rest),*);
    };
    ([$_:ident $(, $id:ident => $val:expr)*], ) => {
        const fn assert_helper< $( $id: Foo ),* >( $( _: [$id; 0] ),* ) {
            const {
                assert!((0 $( + $id::MEMBER )* ) <= 16);
            }
        }
        assert_helper( $( if false { [$val; 0] } else { [] } ),* );
    };
    ($($lst:expr),*) => {
        check_sum!([i], $($lst),*);
    }
}

If you’re going to define assert_helper anew in each macro call, you should put it in a block so it doesn’t pollute the outer scope. (But I would avoid that entirely, and use impls on tuples to make it close enough to variadic. Macros should generate as little code as possible, to reduce the cost of compiling their outputs.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.