我有一个宏,可以包装对
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,因为
&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 {
| +++
那么,在宏中捕获多次写入的写入器的正确方法是什么?
您走在正确的道路上!解决方案是重新借用宏内存储的 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\"");
}