const-concat icon indicating copy to clipboard operation
const-concat copied to clipboard

Support constants that are numbers

Open dtolnay opened this issue 7 years ago • 3 comments

I would like numbers to work in const_concat.

const S: &str = "abc";
const N: u32 = 123;
const C: &str = const_concat!(S, ", ", N); // "abc, 123"

Proof of concept:

#![feature(const_fn, const_fn_union, existential_type, untagged_unions)]

macro_rules! const_cond {
    (default => $e:expr,) => {
        $e
    };
    ($cond:expr => $e:expr, $($rest:tt)*) => {
        const_if($cond, $e, const_cond!($($rest)*))
    };
}

macro_rules! const_to_string {
    ($e:expr) => {{
        const fn const_define() -> This {
            $e
        }

        existential type This: ConstToString;
        const VALUE: This = const_define();
        const FAKE: This = unsafe { transmute::<usize, This>(1) };
        const TYPE: ConstType = <This as ConstToString>::TYPE;

        const IS_STR: bool = const_type_eq(TYPE, ConstType::StaticStr);
        const SEL_STR: This = const_if(IS_STR, VALUE, FAKE);
        const AS_STR: &str = unsafe { transmute::<This, &str>(SEL_STR) };

        const IS_U32: bool = const_type_eq(TYPE, ConstType::U32);
        const SEL_U32: This = const_if(IS_U32, VALUE, FAKE);
        const AS_U32: u32 = unsafe { transmute::<This, u32>(SEL_U32) };

        const DIGITS: [u8; 10] = [
            b'0' + (AS_U32 / 1_000_000_000 % 10) as u8,
            b'0' + (AS_U32 / 100_000_000 % 10) as u8,
            b'0' + (AS_U32 / 10_000_000 % 10) as u8,
            b'0' + (AS_U32 / 1_000_000 % 10) as u8,
            b'0' + (AS_U32 / 100_000 % 10) as u8,
            b'0' + (AS_U32 / 10_000 % 10) as u8,
            b'0' + (AS_U32 / 1_000 % 10) as u8,
            b'0' + (AS_U32 / 100 % 10) as u8,
            b'0' + (AS_U32 / 10 % 10) as u8,
            b'0' + (AS_U32 % 10) as u8,
        ];

        const NUM_STR: &str = unsafe {
            transmute::<&'static [u8], &'static str>(const_cond! {
                AS_U32 >= 1_000_000_000 => &DIGITS,
                AS_U32 >= 100_000_000 => &[DIGITS[1], DIGITS[2], DIGITS[3], DIGITS[4], DIGITS[5], DIGITS[6], DIGITS[7], DIGITS[8], DIGITS[9]],
                AS_U32 >= 10_000_000 => &[DIGITS[2], DIGITS[3], DIGITS[4], DIGITS[5], DIGITS[6], DIGITS[7], DIGITS[8], DIGITS[9]],
                AS_U32 >= 1_000_000 => &[DIGITS[3], DIGITS[4], DIGITS[5], DIGITS[6], DIGITS[7], DIGITS[8], DIGITS[9]],
                AS_U32 >= 100_000 => &[DIGITS[4], DIGITS[5], DIGITS[6], DIGITS[7], DIGITS[8], DIGITS[9]],
                AS_U32 >= 10_000 => &[DIGITS[5], DIGITS[6], DIGITS[7], DIGITS[8], DIGITS[9]],
                AS_U32 >= 1_000 => &[DIGITS[6], DIGITS[7], DIGITS[8], DIGITS[9]],
                AS_U32 >= 100 => &[DIGITS[7], DIGITS[8], DIGITS[9]],
                AS_U32 >= 10 => &[DIGITS[8], DIGITS[9]],
                default => &[DIGITS[9]],
            })
        };

        const STRING: &str = const_cond! {
            IS_STR => AS_STR,
            IS_U32 => NUM_STR,
            default => "ERROR",
        };

        STRING
    }};
}

enum ConstType {
    StaticStr,
    U32,
}

const fn const_type_eq(a: ConstType, b: ConstType) -> bool {
    a as u8 == b as u8
}

unsafe trait ConstToString: Copy {
    const TYPE: ConstType;
}

unsafe impl ConstToString for &'static str {
    const TYPE: ConstType = ConstType::StaticStr;
}

unsafe impl ConstToString for u32 {
    const TYPE: ConstType = ConstType::U32;
}

const fn const_if<T: Copy>(condition: bool, then: T, otherwise: T) -> T {
    [otherwise, then][condition as usize]
}

const unsafe fn transmute<From, To>(from: From) -> To {
    #[repr(C)]
    struct Pad<From> {
        from: From,
        zeros: [u8; 16],
    }

    #[allow(unions_with_drop_fields)]
    union Transmute<From, To> {
        from: Pad<From>,
        to: To,
    }

    Transmute {
        from: Pad {
            from,
            zeros: [0; 16],
        },
    }.to
}

fn main() {
    const N: u32 = 12345;
    const N_TO_STRING: &str = const_to_string!(N);

    const S: &str = "&str";
    const S_TO_STRING: &str = const_to_string!(S);

    println!("{:?}", N_TO_STRING); // "12345"
    println!("{:?}", S_TO_STRING); // "&str"
}

@Vurich

dtolnay avatar Aug 20 '18 06:08 dtolnay

I don't think there's any good way to do this, if we stringify every element it'll just turn "Hello, world" into "\"Hello, world\"". The best thing to do is const_concat!(S, ", ", stringify!(N)).

eira-fransham avatar Sep 06 '18 09:09 eira-fransham

I gave a proof of concept of how this would work in a way that is not "stringify every element." Stringify is not used in the proof of concept.

dtolnay avatar Sep 06 '18 09:09 dtolnay

Sorry, I just skimmed it (I've just got back from holiday and I had a lot to check on). That implementation is great. It's a fun exploitation of const semantics, but unfortunately it's a different fun exploitation of const semantics than this repo was created to show off. I'm not really looking for adding complex features to this repo, it's not supposed to be used as a library.

eira-fransham avatar Sep 06 '18 10:09 eira-fransham