# \`concat!\` can and should support concatting \`const\`

**URL:** https://internals.rust-lang.org/t/concat-can-and-should-support-concatting-const/16414
**Category:** Uncategorized
**Created:** [April 3, 2022, 8:43am UTC](https://internals.rust-lang.org/t/concat-can-and-should-support-concatting-const/16414 "2022-04-03T08:43:43Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)
#### Post date: [April 3, 2022, 8:43am UTC](https://internals.rust-lang.org/t/concat-can-and-should-support-concatting-const/16414/1 "2022-04-03T08:43:43Z")

</div>

It's now possible to write a fairly simple and fully stable `const_concat!`: [[playground]](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=027bb834dfe635a9a62e4a6467f24c4d)

```rust
#![no_std]

use core::mem::ManuallyDrop;

const unsafe fn transmute_prefix<From, To>(from: From) -> To {
    union Transmute<From, To> {
        from: ManuallyDrop<From>,
        to: ManuallyDrop<To>,
    }

    ManuallyDrop::into_inner(
        Transmute {
            from: ManuallyDrop::new(from),
        }
        .to,
    )
}

/// # Safety
///
/// `Len1 + Len2 >= Len3`
#[doc(hidden)]
#[allow(non_upper_case_globals)]
pub const unsafe fn concat<const Len1: usize, const Len2: usize, const Len3: usize>(
    arr1: [u8; Len1],
    arr2: [u8; Len2],
) -> [u8; Len3] {
    #[repr(C)]
    struct Concat<A, B>(A, B);
    transmute_prefix(Concat(arr1, arr2))
}

#[macro_export]
macro_rules! const_concat {
    () => ("");
    ($a:expr) => ($a);

    ($a:expr, $b:expr $(,)?) => {{
        const BYTES: [u8; { $a.len() + $b.len() }] = unsafe {
            $crate::concat::<
                { $a.len() },
                { $b.len() },
                { $a.len() + $b.len() }
            >(
                *$a.as_ptr().cast(),
                *$b.as_ptr().cast(),
            )
        };
        unsafe { ::core::str::from_utf8_unchecked(&BYTES) }
    }};
    
    ($a:expr, $b:expr, $($rest:expr),+ $(,)?) => {{
        const TAIL: &str = $crate::const_concat!($b, $($rest),+);
        $crate::const_concat!($a, TAIL)
    }}
}

#[test]
fn tests() {
    const SALUTATION: &str = "Hello";
    const TARGET: &str = "world";
    const GREETING: &str = const_concat!(SALUTATION, ", ", TARGET, "!");
    const GREETING_TRAILING_COMMA: &str = const_concat!(SALUTATION, ", ", TARGET, "!",);

    assert_eq!(GREETING, "Hello, world!");
    assert_eq!(GREETING_TRAILING_COMMA, "Hello, world!");
}

```

Rust std would use `#[allow_internal_unstable]` or just inline the function definition rather than `#[doc(hidden)]` and prayers. Obviously it'd be _better_ to write `fn concat [...] -> [u8; Len1 + Len2]` rather than having that as a safety invariant, but I want this to be stabilizable with today's features.

A version of this that supports the other types `concat!` accepts and `stringify!`s is available on crates-io as [`const_format::concatcp!`](https://docs.rs/const_format/latest/const_format/macro.concatcp.html).

---

<div class="post-metadata">

### Author: ![chrefr](https://avatars.discourse-cdn.com/v4/letter/c/e480ec/32.png) [@chrefr](https://internals.rust-lang.org/u/chrefr)
#### Post date: [April 3, 2022, 9:37am UTC](https://internals.rust-lang.org/t/concat-can-and-should-support-concatting-const/16414/2 "2022-04-03T09:37:21Z")

</div>

Note that your macro is unsound: it supports creating non-UTF8 strings:

```rust
println!("{:?}", const_concat!(b"\xFF", b"\xFF"));

```

The fix is to ensure `&str` is provided:

```rust
($a:expr, $b:expr $(,)?) => {{
    const A: &str = $a;
    const B: &str = $b;
    const BYTES: [u8; { A.len() + B.len() }] = unsafe {
        $crate::concat::<
            { A.len() },
            { B.len() },
            { A.len() + B.len() }
        >(
            *A.as_ptr().cast(),
            *B.as_ptr().cast(),
        )
    };
    unsafe { ::core::str::from_utf8_unchecked(&BYTES) }
}};

```

---

<div class="post-metadata">

### Author: ![hyeonu](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/hyeonu/32/2162_2.png) [@hyeonu](https://internals.rust-lang.org/u/hyeonu)
#### Post date: [April 3, 2022, 9:37am UTC](https://internals.rust-lang.org/t/concat-can-and-should-support-concatting-const/16414/3 "2022-04-03T09:37:45Z")

</div>

Currently `concat!()` expands to string literal and it's common for other macros to read its content like `include!(concat!(env!("OUT_DIR"), "/out.rs'))`. AFAIK content of the const values are not accessible during the macro expansion stage. Technically it's possible to implement it without breaking change by make it keep returning string literal if it doesn't need any const value. But it would hurt learnability to make it sometimes return string literal but sometimes not.

I think it's nice to add it as another macro with separate name.

---

<div class="post-metadata">

### Author: ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)
#### Post date: [July 2, 2022, 9:38am UTC](https://internals.rust-lang.org/t/concat-can-and-should-support-concatting-const/16414/4 "2022-07-02T09:38:27Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
