How to concatenate a literal string with a const string?

If you're working with Rust 1.46.01 or later, check out the const_format crate on (crates.io | docs.rs).

  • concatcp: Concatenates integers2, bool, and &str constants into &'static str.

  • formatcp: format-like formatting (emits a &'static str); takes the same primitives as concatcp

So for your example, formatcp would provide the most flexible solution and does not require the local variable you mention (I'm assuming you're referring to the heap-allocated String resulting from alloc::fmt::format in the format! macro):

use clap::{Arg, App};  
use const_format::formatcp;  
  
const DEFAULT: &'static str = "1";  

fn main() {  
  let params = App::new("app")  
               .arg(Arg::with_name("p")  
                    .help(formatcp!("parameter p (DEFAULT: {})", DEFAULT)))  
               .get_matches();  
  println!("param p = {}", params.value_of("p").unwrap())  
}

Running with app -h gives

app 

USAGE:
    app [p]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <p>    parameter p (DEFAULT: 1)

Limitations of all macros in the crate:

  • Macros that expand to &'static str (i.e. the ones I mentioned) only accept constants from concrete types:
    • Type::<u8>::FOO OK ✅
    • Type::<TYPE_PARAMETER>::FOO BAD ❌
  • Integer arguments need to be constrained (i.e. must add i*/u* suffix when using literals).
  • Can't be used in some places that take string literals only, namely attributes which can require LiteralExpression.
    • #[doc = "ab"] != #[doc = concatcp!("a", "b")]

1 This is needed for the stabilized const fn improvements, which allow for looping without "heinous hackery" involving std::mem::transmute
2 More specifically, this would be all the i*/u* primitives. Note that if you'll have to constrain the type yourself by using the suffix desired if you're passing in literals.


You can simply use a reference and the format! macro:

.help(&format!("this parameter is for (default: {})", DEFAULT_VALUE));

Edit:

What you want to do is not possible in Rust:

This is a fundamental limitation of macros in that they are working with nothing more than various tokens. They have no understanding of types, just tokens that look like types. When concat! sees DESCRIPTION it just sees an identifier, it has no idea that it is a string constant. What could work here though is some sort of string concatenation const fn as that could take the values of constants to create new constants, although that would require some dark magic.

You could do this instead:

macro_rules! DEFAULT {
    () => { "1" };
}

fn main() {
    let params = App::new("app")
        .arg(Arg::with_name("p")
             // here 100 lines of the uninterruptable expression
             .help(concat!("parameter p (DEFAULT: ", DEFAULT!(), ")")))
             // here also 100 lines of the uninterruptable expression
        .get_matches();
    println!("param p = {}", params.value_of("p").unwrap())
}

The use of a macro instead of a constant allows you to use the concat! macro.

Tags:

Rust