我有一个宏,它包装了对的多个调用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]
}
现在,我想正确捕捉作者,但我正在努力让它在所有情况下都发挥作用。即:
- 如果传递了一个表达式,则应该只对其进行一次求值,这样就可以正常
write_twice(fs::File::create(path)?, args...)
工作而无需第二次写入重新截断文件。 - 宏调用之后,编写器应该仍然可用,即
$writer
不应被移动。 - 如果 的实例可以
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 因为 a&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 {
| +++
如果我们尝试let mut writer = &mut *$writer
,我们将无法再调用,write_twice!(string, ...)
因为我们现在尝试写入 a&mut str
而不是 a &mut String
。
那么,在多次写入的宏中捕获写入器的正确方法是什么?