Concatenating string literals


#1

Is there a way to do it? The concat macro only gives me static slices which I can’t use a the format string for write.

Here’s what I want to do: I have a struct I want to print out nicely (implementing Display), say each element on a new line. The format string would look like “Name: {}\n Project: {}”, but I have a lot of elements. I guess I could write it down like

 write(f, "Name: {}\n Project: {} \n ...", struct.name, struct.project)

but that will be kind of unwieldy and hard to parse. I have thought about using a macro:

macro_rules! table_format_str {
    ( $ele: expr) => {
        concat!($ele, ": {}\n")
    };
    ( $ele: expr, $( $rest: expr ),+)  => {
        concat!($ele, ": {}\n", table_format_str!($($rest),*))
    };
}

so I could call

table_format_str("Name", "Project")

but since concat does not return literals, my macro doesn’t as well.

So I guess my question might turn into “How to print out large structs nicely?”, if I can’t concat string literals.

Thanks for any help :slight_smile:


#2

Yes concat! produces string literals which can be used with write!.

use std::io::Write;

macro_rules! table_format_str {
    ($ele: expr) => {
        concat!($ele, ": {}\n")
    };
    ($ele: expr, $($rest: expr),+)  => {
        concat!($ele, ": {}\n", table_format_str!($($rest),*))
    };
}

struct KillTheMule {
    name: String,
    project: String,
}

fn main() {
    let mut f = std::io::stdout();
    let s = KillTheMule { name: "n".to_owned(), project: "p".to_owned() };
    write!(f, table_format_str!("Name", "Project"), s.name, s.project).unwrap();
}

#3

Are you sure? This program works for me (playground):

use std::io::Write;
write!(std::io::stdout(), concat!("hello ", "{}"), "world");

Another option for your use case is a multi-line string literal:

write!(f, "Name: {}\n\
           Project: {}\n\
           ...", struct.name, struct.project, ...)

\ at the end of a line suppresses the newline and any leading whitespace on the next line. For details, see the reference section on string literals.


#4

Ahhh thanks, my main looked like

...
let r = table_format_str!("Name", "Project");
write!(f, r, s.name, s.project).unwrap();
...

which would not work because r was supposedly not a string literal. Do you know why that is?


#5

Ahhh awesome, that looks pretty much what I’d have wanted anyways, thanks!


#6

r is not a string literal; it’s a variable that is bound to a string literal.


#7

Macro, not function, right, gotcha!

Any way to fix this? It would kinda feel comfortable to bind a literal to a variable, construct it in some more-or-less elaborate way and still use it correctly in a macro.


#8

Maybe have it emit a macro?

table_format_str!(r = "Name" "Project");
r!(f, s.name, s.project).unwrap();

#9

Nice idea, thanks :slight_smile:


#10

I feel like generating a custom formatter string using macros might be an X-Y problem. Can’t you just use the debug_* methods on a std::fmt::Formatter to implement Display and only show the attributes you want?

Here’s the example for Formatter::debug_struct() to show what I mean:

use std::fmt;

struct Foo {
    bar: i32,
    baz: String,
}

impl fmt::Debug for Foo {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Foo")
            .field("bar", &self.bar)
            .field("baz", &self.baz)
            .finish()
    }
}

// prints "Foo { bar: 10, baz: "Hello World" }"
println!("{:?}", Foo { bar: 10, baz: "Hello World".to_string() });

#11

Hmm I don’t see how to add custom formating to that, how would you print that example as

bar: 10
baz: Hello World!

#12

Ah okay, I see what you are trying to do. You’d probably need to make your own custom formatter and then use that to write directly to the fmt::Formatter, although it’s probably not worth the effort unless this is going to be something you use a lot.


#13

Yeah I know how to do this basically, but I’m looking for a nice/convenient/elegant way to construct the format string, which needs to be a string literal and therefore not accessible via the methods one can use on a string.

That final slash for a multiline string to also ignore the leading whitespaces pretty much does it for me, so… success!