Layui

println! error: expected a literal / format argument must be a string literal

匿名 (未验证) 提交于 2018-03-17 14:50:30

问题:

This extremely simple Rust program:

fn main() {
    let c = "hello";
    println!(c);
}

throws the following compile-time error:

error: expected a literal
 --> src/main.rs:3:14
  |
3 |     println!(c);
  |              ^

In previous versions of Rust, the error said:

error: format argument must be a string literal.
     println!(c);
              ^

Replacing the program with:

fn main() {
    println!("Hello");    
}

Works fine.

The meaning of this error isn't clear to me and a Google search hasn't really shed light on it. Why does passing c to the println! macro cause a compile time error? This seems like quite unusual behaviour.

回答1:

The reason that

fn main() {
    let c = "hello";
    println!(c);
}

Cannot work is because the println! macro looks at the string at compile time and verifies that the arguments and argument specifiers match in amount and type (this is a very good thing!). At this point in time, during macro evaluation, it's not possible to tell that c came from a literal or a function or what have you.

Here's an example of what the macro expands out to:

let c = "hello";
match (&c,) {
    (__arg0,) => {
        #[inline]
        #[allow(dead_code)]
        static __STATIC_FMTSTR: &'static [&'static str] = &[""];
        ::std::io::stdio::println_args(&::std::fmt::Arguments::new(
            __STATIC_FMTSTR,
            &[::std::fmt::argument(::std::fmt::Show::fmt, __arg0)]
        ))
    }
};

From my comment on this answer:

I don't think that it's actually impossible for the compiler to figure this out, but it would probably take a lot of work (potentially for little gain). Macros operate on portions of the AST, which I assume only has type information. To work in this case, the AST would have to include the source of the identifier and enough information to determine it's "safe". In addition, it might interact poorly with type inference - you'd want to know the type before it's been picked yet!

From my comment on the other answer:

The error message asks for a "string literal". There's a SO question about what that means, which links to the Wikipedia entry:

a literal is a notation for representing a fixed value in source code

"foo" is a string literal, 8 is a numeric literal. let s = "foo" is a statement that assigns the value of a string literal to an identifier (variable). println!(s) is a statement that provides an identifier to the macro.



回答2:

This should work:

fn main() {
  let c = "hello";
  println!("{}", c);
}

The string "{}" is a template where {} will be replaced by the next argument passed to println!.



回答3:

If you really want to define the first argument of println! in one place, I found a way to do it. You can use a macro:

macro_rules! hello {() => ("hello")};
println!(hello!());

Doesn't look too useful here, but I wanted to use the same formatting in a few places, and in this case the method was very helpful:

macro_rules! cell_format {() => ("{:<10}")};  // Pads with spaces on right
                                              // to fill up 10 characters
println!(cell_format!(), "Foo");
println!(cell_format!(), 456);

The macro saved me from having to duplicate the formatting option in my code.

You could also, obviously, make the macro more fancy and take arguments if necessary to print different things with different arguments.



回答4:

If your format string will be reused only a moderate number of times, and only some variable data will be changed, then a small function may be a better option than a macro:

fn pr(x){
    println!("Some stuff that will always repeat, something variable: {}",x);
};

pr("I am the variable data".to_string());

Outputs

Some stuff that will always repeat, something variable: I am the variable data