如何在包装多个“write!”调用的宏中正确捕获 writer

问题描述 投票:0回答:1

我有一个宏,可以包装对

write!
的多个调用。对于一个玩具示例,假设我们有这个宏:

use std::fmt::Write;

macro_rules! write_twice {
    ($writer:expr, $($fmt:tt)*) => {{
        (|| {
            for _ in 0..2 {
                if let err @ ::core::result::Result::Err(_) = write!($writer, $($fmt)*) {
                    return err;
                }
            }
            Ok(())
        })()
    }};
}

fn main() {
    let v = vec![1, 2];
    let mut buf = String::new();
    write_twice!(&mut buf, "{v:?}").unwrap();
    println!("{buf:?}");  // [1, 2][1, 2]
}

现在,我想正确地捕捉作者,但我正在努力使其在所有情况下都有效。即:

  1. 如果传递了一个表达式,则只应对其求值一次,这样像
    write_twice(fs::File::create(path)?, args...)
    这样的东西就可以工作,而无需第二次写入重新截断文件。
  2. 编写器在宏调用后应该仍然可用,即
    $writer
    不应该被移动。
  3. 如果
    write!(w, args...)
    的实例可以编译,那么当替换为
    write_twice!(w, args...)
    时它也应该可以编译。

现在,上述版本的

write_twice!
失败了#1,因为
write!($writer, $($fmt)*)
将被评估两次。我们可以用这个替换它:

macro_rules! write_twice {
    ($writer:expr, $($fmt:tt)*) => {{
        (|| {
            let mut writer = $writer;  // evaluate only once
            for _ in 0..2 {
                if let err @ ::core::result::Result::Err(_) = write!(writer, $($fmt)*) {
                    return err;
                }
            }
            Ok(())
        })()
    }};
}

但这违反了#2,因为

&mut T
不是
Copy
:

use std::fmt;

struct Double(String);

impl fmt::Display for Double {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "first -> ")?;
        write_twice!(f, "{}", self.0)?;
        write!(f, " <- second")  // borrow of moved value: `f`
    }
}
error[E0382]: borrow of moved value: `f`
  --> src/main.rs:23:10
   |
5  |         (|| {
   |          -- value moved into closure here
...
20 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
   |                   - move occurs because `f` has type `&mut Formatter<'_>`, which does not implement the `Copy` trait
21 |         write!(f, "first -> ")?;
22 |         write_twice!(f, "{}", self.0)?;
   |                      - variable moved due to use in closure
23 |         write!(f, " <- second")
   |                ^ value borrowed here after move

然后我尝试用

let writer = &mut $writer;
替换该行以避免移动
&mut T
,但现在我们收到一个新的编译器错误,违反了#3(尽管编译器的建议确实解决了问题):

error[E0596]: cannot borrow `f` as mutable, as it is not declared as mutable
  --> src/main.rs:6:17
   |
6  |             let writer = &mut $writer;
   |                          ^^^^^^^^^^^^ cannot borrow as mutable
...
22 |         write_twice!(f, "{}", self.0)?;
   |         ----------------------------- in this macro invocation
   |
   = note: this error originates in the macro `write_twice` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider changing this to be mutable
   |
20 |     fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
   |                   +++

那么,在宏中捕获多次写入的写入器的正确方法是什么?

rust macros
1个回答
0
投票

您走在正确的道路上!解决方案是重新借用宏内存储的 writer:

use std::fmt::{Write, Display, Formatter, self};

macro_rules! write_twice {
    ($writer:expr, $($fmt:tt)*) => {{
        (|| {
            let writer = &mut *$writer;
            for _ in 0..2 {
                if let err @ ::core::result::Result::Err(_) = write!(writer, $($fmt)*) {
                    return err;
                }
            }
            Ok(())
        })()
    }};
}

fn main() {
    let v = vec![1, 2];
    let mut buf = String::new();
    let mut evals = 0;
    write_twice!({
        evals += 1;
        &mut buf
    }, "{v:?}").unwrap();
    
    // Case #1
    assert_eq!(evals, 1, "#1");

    // Case #2
    #[allow(unused)]
    struct Double(String);
    impl Display for Double {
        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
            write!(f, "first -> ")?;
            write_twice!(f, "{}", self.0)?;
            write!(f, " <- second")  // borrow of moved value: `f`
        }
    }
    
    // Case #3
    write!(&mut buf, "#2").unwrap();
    
    assert_eq!(format!("{buf:?}"), "\"[1, 2][1, 2]#2\"");
}
© www.soinside.com 2019 - 2024. All rights reserved.