With the recent improvements to const fn it is possible to concat &str into &[u8]. It still needs a macro but not a procedural macro. However, I'm not sure how to get a &str output at compile time. I think you need to do that conversion at runtime:
macro_rules! combine {
($A:expr, $B:expr) => {{
const A: &str = $A;
const B: &str = $B;
const LEN: usize = A.len() + B.len();
const fn combined() -> [u8; LEN] {
let mut out = [0u8; LEN];
out = copy_slice(A.as_bytes(), out, 0);
out = copy_slice(B.as_bytes(), out, A.len());
out
}
const fn copy_slice(input: &[u8], mut output: [u8; LEN], offset: usize) -> [u8; LEN] {
let mut index = 0;
loop {
output[offset+index] = input[index];
index += 1;
if index == input.len() { break }
}
output
}
const RESULT: &[u8] = &combined();
// how bad is the assumption that `&str` and `&[u8]` have the same layout?
const RESULT_STR: &str = unsafe { std::mem::transmute(RESULT) };
RESULT_STR
}}
}
const BASE: &str = "path/to";
const PART: &str = "foo";
const PATH: &str = combine!(BASE, PART);
fn main() {
let s = PATH;
dbg!(s);
}
Edit: Maybe this is an improvement:
const RESULT: &[u8] = &combined();
const RESULT_P1: *const [u8] = RESULT;
// AKAIK `as`-casts take pointer layout into account
const RESULT_P2: *const str = RESULT_P1 as *const str;
// hence, this approach seems somewhat more sane to me
// even though in non-const code I would of course
// prefer to use a borrow+dereference
const RESULT_STR: &str = unsafe { std::mem::transmute(RESULT_P2) };
RESULT_STR
That's great! I think your version should be safe in practice. But I'm not qualified to say if it's safe in future versions of Rust. But hopefully in the future these kinds of hacks will be less necessary.
From what I understand, "&str has the layout of &[u8]" is de facto guaranteed by the existence of stable methods like as_bytes_mut(). Or for that matter, like str::from_utf8_unchecked() (which is just a transmute like yours, and unstably const: issue 75196).
I'll just point out that concatc requires Rust nightly, you can use concatcp in stable Rust (since 1.46.0) for &'static str and integer arguments though.
Don't know the best way to document whether an item requires Rust nightly or not, right now it's documented in the root module.
Perhaps using doc_cfg on docs.rs, you could make more visible which macro requires which feature (and thus which ones require nightly and which don’t). Look at e.g. the tokio crate for how that works.
Edit: You also need to set up the Cargo.toml as described here (the part about [package.metadata.docs.rs]). And you can also test what the resulting documentation looks like locally by passing --cfg docsrs manually (make sure to also activate all features and run cargo doc on nightly for the local test).