Tenho uma macro que encapsula várias chamadas para write!
. Para um exemplo de brinquedo, suponha que temos esta macro:
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]
}
Agora, eu gostaria de capturar o escritor corretamente, mas estou lutando para fazer isso funcionar em todos os casos. A saber:
- Se uma expressão for passada, ela deverá ser avaliada apenas uma vez, para que algo como
write_twice(fs::File::create(path)?, args...)
funcione sem que a segunda gravação retrunque o arquivo. - O escritor ainda deve ser utilizável após a chamada da macro, ou seja,
$writer
não deve ser movido. - Se uma instância de
write!(w, args...)
compila, ela também deve compilar quando substituída porwrite_twice!(w, args...)
.
Agora, a versão acima de write_twice!
falhas #1, como write!($writer, $($fmt)*)
será avaliada duas vezes. Poderíamos substituí-la por isto:
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(())
})()
}};
}
Mas isso viola o nº 2 porque a &mut T
não é 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
Então tentei substituir essa linha por let writer = &mut $writer;
para evitar mover o &mut T
, mas agora temos um novo erro do compilador, violando o nº 3 (embora a sugestão do compilador corrija o problema):
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 {
| +++
E se, em vez disso, tentarmos let mut writer = &mut *$writer
, não poderemos mais chamar write_twice!(string, ...)
porque agora estamos tentando escrever para a &mut str
em vez de a &mut String
.
Então, qual é a maneira correta de capturar um escritor em uma macro que grava nele várias vezes?