Idiomatic way to concatenate `const &str` and also byte string?

Dear Rustacean,

Is it possible to build const &str by concatenatingconst &str?
Here's my code snippet that doesn't work.

const DIR: &str = "/data/tmp/local/test";   // will be used to build other const &str.
const FILE1: &str = "a.txt";    // will be used to build other const &str
const FILE2: &str = "b.txt";
const FILE3: &str = "c.txt";

const PATH1: &str = concat!(DIR, FILE1);   // will be used as Path
const PATH2: &str = concat!(DIR, FILE2);
const PATH3: &str = concat!(DIR, FILE3);

const C_PATH1: &[u8] = concat_bytes!(DIR, FILE1);    // will be used as core::ffi::CStr
const C_PATH2: &[u8] = concat_bytes!(DIR, FILE2);    // will be used as core::ffi::CStr
const C_PATH3: &[u8] = concat_bytes!(DIR, FILE3);    // will be used as core::ffi::CStr

During I write this code, I could find that Rust only have small subset of allowed functions for const and const variables aren't treated as literal.

However, I used to do the similar when I wrote code in C or Java. What would be a ideomatic way to achieve the same? I could achieve the goal at the runtime, but I'm looking for a way to generate those strings/slices at the compile time.

You can use constcat to do some simple stuff. const_format is more powerful but I don't think works with byte slices

constcat seems to work for your example at least

use constcat::{concat, concat_bytes};

const DIR: &str = "/data/tmp/local/test"; // will be used to build other const &str.
const FILE1: &str = "a.txt"; // will be used to build other const &str
const FILE2: &str = "b.txt";
const FILE3: &str = "c.txt";

const PATH1: &str = concat!(DIR, FILE1); // will be used as Path
const PATH2: &str = concat!(DIR, FILE2);
const PATH3: &str = concat!(DIR, FILE3);

const C_PATH1: &[u8] = concat_bytes!(DIR.as_bytes(), FILE1.as_bytes()).as_slice(); // will be used as core::ffi::CStr
const C_PATH2: &[u8] = concat_bytes!(DIR.as_bytes(), FILE2.as_bytes()).as_slice(); // will be used as core::ffi::CStr
const C_PATH3: &[u8] = concat_bytes!(DIR.as_bytes(), FILE3.as_bytes()).as_slice(); // will be used as core::ffi::CStr

fn main() {
    for str in [PATH1, PATH2, PATH3] {
        println!("{}", str)
    }

    for bytes in [C_PATH1, C_PATH2, C_PATH3] {
        println!(
            "{:?}",
            bytes
                .iter()
                .map(|i| char::from_u32(*i as u32).unwrap())
                .collect::<Vec<_>>()
        )
    }
}
3 Likes

Thank you for the crate. The crate has all I wanted, and I learned a lot.

Note that if one is willing to create a value with a 'static lifetime, i.e. a value that isn't cleaned up until program termination, then creating a &'static str can be done at runtime too:

let val: String = format!("The real answer: {}", 42);
let val: Box<str> = val.into_boxed_str();
let val: &'static str = Box::leak(val); // et voilà, a runtime-created &'static str that can contain arbitrary contents. 

Note that in this context the name Box::leak() is a little misleading depending on how the code snippet is used: use it often and that can effectively turn into leaking heap memory. But with some care, it can also be used eg for value interning, or efficiently creating a small pool of values, and just dole out &'static T borrows to those values, which are cheap and Copy, making them pretty ergonomic to use as well.

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.