【问题标题】:println! error: expected a literal / format argument must be a string literal打印!错误:预期文字/格式参数必须是字符串文字
【发布时间】:2015-01-01 21:10:14
【问题描述】:

这个极其简单的 Rust 程序:

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

抛出以下编译时错误:

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

在之前的Rust版本中,报错说:

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

将程序替换为:

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

工作正常。

我不清楚这个错误的含义,谷歌搜索也没有真正阐明它。为什么将c 传递给println! 宏会导致编译时错误?这似乎是非常不寻常的行为。

【问题讨论】:

    标签: rust


    【解决方案1】:

    这应该可行:

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

    字符串"{}" 是一个模板,其中{} 将被传递给println! 的下一个参数替换。

    【讨论】:

    • 这确实有效,虽然它有点难看!为什么会发生原始错误?以这种方式打印字符串在几乎所有现代语言中都是一项非常常见的任务,并且给出的错误消息非常模棱两可。
    • 这并不模棱两可,但它确实假设您知道“字面量”是什么。 "foo" 是一个字符串文字。 8 是一个数字文字。 let s = "foo" 将字符串文字的值分配给变量,println!(s) 将变量传递给宏。
    • @Shepmaster 谢谢 - 你能提供一个答案,提供更多细节吗?我希望许多其他 rust 用户会遇到来自 Python 等语言的此错误,如果您的解释会有所帮助。
    • 什么宏检查是文字(字符串、数字等),而用 let 分配的变量不是这些东西。
    • 不期望来自新语言的你应该只是猜测函数是如何工作的。我的意思是,比较 C++ 的 std::cout,它的语法大不相同。学习一门新语言意味着阅读教程和文档,并且只使用你所学的内容。否则你可能会遇到意想不到的行为,甚至更糟糕的是,安全漏洞(或者只是奇怪的错误,比如使用可变数组函数的返回值,却没有意识到它改变了原始数组。Rust 的 mut 修复了该问题,但其他语言没有 @ 987654331@).
    【解决方案2】:

    TL;DR 如果您不关心为什么而只想修复它,请参阅the sibling answer


    原因

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

    不能工作是因为println! 宏在编译时查看字符串 并验证参数和参数说明符在数量和类型上是否匹配(这是一件非常好的事情!)。目前,在宏评估期间,无法判断 c 来自文字或函数或您有什么。

    以下是宏扩展为的示例:

    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)]
            ))
        }
    };
    

    我认为编译器实际上不可能弄清楚这一点,但它可能需要大量工作而可能收效甚微。宏对部分 AST 进行操作,而 AST 只有类型信息。要在这种情况下工作,AST 必须包含标识符的来源和足够的信息来确定它是否可以用作格式字符串。此外,它可能与类型推断的交互很差 - 您可能想在类型被选中之前就知道它!

    错误消息要求输入“字符串文字”。 What does the word "literal" mean? 询问这是什么意思,链接到Wikipedia entry

    字面量是源代码中表示固定值的符号

    "foo" 是字符串文字,8 是数字文字。 let s = "foo" 是将字符串文字的值分配给标识符(变量)的语句。 println!(s) 是为宏提供标识符的语句。

    【讨论】:

    • 为什么在编译时无法判断 c 来自编译时的文字?
    • 我认为真正的问题是,如果 c = "{}" 和宏在编译时被解析,编译器很难确定它是否会出现进一步的参数,所以在编译时无法保证验证...
    • 我认为这并非不可能永远,只是现在宏的实现方式并非不可能。宏采用portions of the AST,我假设它只有类型信息。您的问题将涉及了解类型的来源和足够的信息来确定它是“安全的”。此外,它可能与类型推断的交互很差 - 您想在选择之前知道类型!
    • 谢谢,我想我现在明白了(或者我不那么困惑了)-@Shepmaster,您能否将有关宏和 AST 的内容添加到您的答案中?非常有用的信息。
    • 我认为通过这项工作可以获得一些收益。 log4rs 使用相同的宏方案。任何时候错误字符串在函数的结果中被踢回并且您想用error!(err) 记录它,您不能。你必须使用error!("{}", err) hack。这不是世界末日,但它是一种温和的刺激物。 :)
    【解决方案3】:

    如果您的格式字符串只会被重复使用中等次数,并且只会更改一些可变数据,那么小函数可能是比宏更好的选择:

    fn pr(x: &str) {
        println!("Some stuff that will always repeat, something variable: {}", x);
    }
    
    pr("I am the variable data");
    

    输出

    一些总是重复的东西,一些可变的东西:我是可变数据

    【讨论】:

      【解决方案4】:

      如果你真的要定义 println 的第一个参数!在一个地方,我找到了一种方法。您可以使用宏:

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

      在这里看起来不太有用,但我想在几个地方使用相同的格式,在这种情况下,该方法非常有用:

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

      宏使我不必在代码中复制格式选项。

      显然,您还可以使宏更花哨,并在必要时使用参数来打印具有不同参数的不同内容。

      【讨论】:

      • 如果你打算使用宏方法,你不妨把println!放在宏里面,就像macro_rules! cell_format { ( $e:expr ) =&gt; ( println!("{:&lt;10}", $e) ); }
      猜你喜欢
      • 2020-12-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-20
      • 1970-01-01
      • 2023-04-09
      • 2020-05-05
      • 2017-05-06
      • 2019-11-04
      相关资源
      最近更新 更多