Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to concatenate a literal string with a const string?

Tags:

rust

I am trying to instantiate an argument parser (clap). There is code like:

const DEFAULT_VALUE: &'static str = "12312312";
// ...
.help("this parameter is for (default:" + DEFAULT_VALUE + ")")
// ...

I looked through similar existing questions and discovered concat! macro and lazy_static.

First option doesn't fit and there is no example for lazy_static. If it's possible it's will be over complicated anyway because lazy_static requires to define a block in a separate place.

I am looking some concise syntax sugar with a macro in place, without a lot of type overhead.

If define a local variable it could get to high because DSL for clap could be very long. It's not convenient, because it rips the string from its logical place in code.

Another approach to define static variable for the whole help string but it posses the same drawback as the approach above plus namespace pollution.


Suggested solution with format! doesn't fit too. It requires to define a local variable.


Example

extern crate clap;

use clap::{Arg, App};

const DEFAULT: &'static str = "1";

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

Cargo.toml

[package]

name = "demo-clap"
version = "1.0.0"
[dependencies]

clap = "2.10.0"

Compilation error

<std macros>:2:1: 2:61 error: borrowed value does not live long enough
<std macros>:2 $ crate :: fmt :: format ( format_args ! ( $ ( $ arg ) * ) ) )
               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/main.rs:11:21: 11:66 note: in this expansion of format! (defined in <std macros>)
src/main.rs:13:24: 15:2 note: reference must be valid for the block suffix following statement 0 at 13:23...
src/main.rs:13         .get_matches();
                                      ^
src/main.rs:8:5: 13:24 note: ...but borrowed value is only valid for the statement at 8:4
src/main.rs:8     let params = App::new("app")
                  ^
src/main.rs:8:5: 13:24 help: consider using a `let` binding to increase its lifetime
src/main.rs:8     let params = App::new("app")
                  ^
error: aborting due to previous error
error: Could not compile `demo-clap`.
like image 716
Daniil Iaitskov Avatar asked Aug 18 '16 16:08

Daniil Iaitskov


People also ask

Can you concatenate string literals?

Concatenate multiple stringsYou can combine both string variables and string literals using the “+” operator.

Are string literals const?

String constants, also known as string literals, are a special type of constants which store fixed sequences of characters. A string literal is a sequence of any number of characters surrounded by double quotes: "This is a string."

How do you combine strings?

Concatenation is the process of appending one string to the end of another string. You concatenate strings by using the + operator. For string literals and string constants, concatenation occurs at compile time; no run-time concatenation occurs.

Can you concatenate a char to a string in C++?

C++ has a built-in method to concatenate strings. The strcat() method is used to concatenate strings in C++. The strcat() function takes char array as input and then concatenates the input values passed to the function.


2 Answers

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.

like image 108
antoyo Avatar answered Oct 28 '22 07:10

antoyo


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.

like image 30
YenForYang Avatar answered Oct 28 '22 05:10

YenForYang