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`.
Concatenate multiple stringsYou can combine both string variables and string literals using the “+” operator.
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."
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.
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.
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.
If you're working with Rust 1.46.01 or later, check out the const_format
crate on (crates.io | docs.rs).
concatcp
: Concatenates integers
2, 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:
&'static str
(i.e. the ones I mentioned) only accept constants from concrete types:
Type::<u8>::FOO
OK ✅Type::<TYPE_PARAMETER>::FOO
BAD ❌i*/u*
suffix when using literals).#[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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With