Rust: concat doesn't allow string constants as parameters

Created on 3 Feb 2016  路  20Comments  路  Source: rust-lang/rust

Scenario: I've defined a description and a version constant:

const DESCRIPTION: &'static str = "my program";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");

Now I want to define a version string which I'll print when the -v argument is passed:

const VERSION_STRING: &'static str = concat!(DESCRIPTION, " v", VERSION);

This currently does not work, which is not intuitive. I'd expect static constants to work just like string literals. If you want to concatenate string constants to form another string constant, you currently have to define a macro for each constant:

macro_rules! description {
    () => ( "my program" )
}
macro_rules! version {
    () => ( env!("CARGO_PKG_VERSION") )
}
macro_rules! version_string {
    () => ( concat!(description!(), " v", version!()) )
}

Most helpful comment

It's been a while - is there any such feature request available in the RFCs repo? If there is, it'd be nice to reference it from this issue.

All 20 comments

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.

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.

actually that would be pretty cool.. something like "intrinsic constant functions"

This is a fundamental limitation of macros

OK, I already suspected that.

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.

So something like constexpr functions in C++? Sounds like a good idea.

@oli-obk intrinsic const fns could actually happen. There's a few good candidates, size_of being one. Some sort of intrinsic string-concatenation function should be possible, though a bit trickier since you do actually have to store the data somewhere in order to return a reference to it.

Closing as "not a bug".

@steveklabnik Is there any other way to track the status of this feature request?

@CodeMonkey90 Feature requests usually belong in the rfcs repo.

It's been a while - is there any such feature request available in the RFCs repo? If there is, it'd be nice to reference it from this issue.

@oli-obk since const evaluation has come a long way, would it be possible use that to combine string constants into a new one? Or would that require const fn std::ops::Add or similar?

Also, generally const fns need to also be usable at runtime with normal runtime values. With constants it isn't too hard with some compile black magic but how would a fn(&str, &str) -> &str work at runtime? Would it just construct a String and then leak it?

I would not want to see such a function. I'd rather make the various methods on String const fn and use push and push_str. This requires heap allocations at compile time and indeed a way to leak the final value. This most definitely needs an RFC though. The implementation of the feature is rather trivial, implementing the restrictions to make it sane requires some thought.

If searches lead anyone to this issue, it's worth noting that @Vurich has found a way to implement a concat macro that accepts constants in the const-concat crate (github, crates.io). It is very nightly-only though and uses "heinous hackery" 馃槃

The "best" (or at least working) solution I've seen so far is through usage of quote! macro in procedural macro crates.

FWIW you can just work around this by having a "constant" macro as such:

macro_rules! A {() => {"a"}}
const A: &'static str = A!();
macro_rules! B {() => {"b"}}
const B: &'static str = B!();
const AB: &'static str = concat!(A!(), B!());

Why was this issue closed? There are indeed workarounds but they are just looking ugly.
In the ideal world, the below should work
const MY_URL: &str = "A";
const MY_RE: &str = "^" + MY_URL + "$";
Now my code has a mix of constants and macro calls.

@sergeken Because concat! cannot be changed to do this. However a const fn way of concatenating strings is possible. https://github.com/rust-lang/const-eval/issues/20 is probably the best place to follow for further developments.

const fn and others could be a way. However, these seems to be used to have runtime generated constants. My case is about compiler evaluated constant expression. My example is to combine a set of string literals at compile time. I agree it is kind of syntax sugar but high level languages are about giving stronger expressiveness. Alternatively the two consts can be written without the "+" operator but this is not following the DRY principle.
As I think about it, this issue is not only about the const keyword but adding the "+" concatenation operator to string literals. My example is actually coming from the desire to write: let re = Regex::new("^" + MY_URL + "$");. The use of concact! is actually a workaround to my initial comment. And I hope you can see from the example the value of being able to allow such expressions.

It is possible to implement a macro to concatenate &'static str constants in Rust 1.46.
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=1555757f31d8b98f514806c4f94b634c
Example use of the macro:

fn main(){
    const A: &str = "hello";
    const B: &str = " world";
    const C: &str = concat_strs!(A, B, B, B, B);
    dbg!(C);
}

prints

[src/main.rs:56] C = "hello world world world world"

Limitations

The linked macro cannot concatenate constants that come from generic parameters.

Examples of what it can't do

It can't concatenate const parameters

fn foo<const S: &'static str>(){
       concat_strs!(S, S);
}

It can't concatenate associated constants of type parameters:

fn foo<S: Foo>(){
       concat_strs!(S::BAR, "");
}

This is exactly what I want. Next step is to add syntactic sugar to replace the macro with a '+' operator :-). However, I'm happy with the current syntax for declaring const's.
Is there any plan to enhance the original concat macro? Or will this be a breaking change?

The thing with making the concat macro concatenate constants as well is that it would have to do two things:

  • Expand to a literal when it's given literals.
  • Expand to code that computes the concatenated &'static str when it's given at least one constant.

I think that might be possible? I don't really know.

Was this page helpful?
0 / 5 - 0 ratings