-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow storing format_args!
in a let binding
#92698
Comments
This would be fixed by this accepted RFC: #15023. |
So I ran into this issue, but at the same time I managed to find a rather nifty workaround.
For me this works and compiles on stable 1.61. |
Yes, this looks pretty much like the same hack fern is using with its |
An exercise in futility, since you're calling If you really want to get the length of the outputted string without allocating, you could try calculating it manually, e.g. |
At the very least this surprising behavior should be better documented. And maybe, the error message should give a better recommendation of what to do when this happens for The suggestion of changing let binding = format_args!("{}", thing);
let args = binding; isn't really helpful. |
With the changes made to |
Nope, that's unrelated. format_args's expansion includes temporaries whose lifetimes need to be extended. We currently don't have a flexible way to do that. Temporary lifetime extension only applies to very few types of expressions, which don't include function calls. See also some related thoughts here on zulip: https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/design.20meeting.202023-03-15.3A.20temporary.20lifetimes/near/351415051 |
Yes the error message is very misleading and doesn't make sense. We can add this stackoverflow question to the list of confused people. We could at least link this issue in the documentation of |
To make a deeply nested let y = format_args!("<{x}>");
println!("{y}"); As noted above, this gives
Weird suggestion, but if I follow it let binding = format_args!("<{x}>");
let y = binding;
println!("{y}"); It chokes on its own previous suggestion, giving an even weirder one, as it doesn't see
So I thought: if some temporary is too short lived, let me turn it all into one statement. Essentially like @HindrikStegenga's passing it to a closure, but in natural order if let y = format_args!("<{x}>") {
println!("{y}");
} Even though this works as desired, it gives
|
Until this gets solved, I wrapped @HindrikStegenga's closure in natural syntax: macro_rules! let_workaround {
(let $name:ident = $val:expr; $($rest:tt)+) => {
let f = |$name| { // naming closure avoids #[allow(clippy::redundant_closure_call)]
let_workaround! { $($rest)+ }
};
f($val)
};
($($rest:tt)+) => { $($rest)+ }
}
fn main() {
let (a, b, c) = (1, 2, 3);
let f_ab = let_workaround! {
let f_a = format_args!("a {a}");
let f_b = format_args!("b {b}");
format!("{f_a}, {f_b}") // hidden closure can't return format_args 😟
}; // return value: semicolon on outer statement
let_workaround! {
let f_c = format_args!("c {c}");
let f_abc = format_args!("{f_ab}, {f_c}");
println!("{f_abc}");
} // no return value: no semicolon on block
println!("done");
} What this can't solve, is the lack of if something { format_args!("a {a}") }
else { format_args!("b {b}") } |
Turns out clippy doesn't like this. What clippy misses here, is that this currently seems to be the only way to extend the temp lifetimes. So clippy is wrong. 👎 Edit 1: Added an annotation above, to silence this clippy warning. Or try @dtolnay's workaround below. However that's semantically the same as my Edit 2: Changing the above again, as I didn't want the annotation to also be on the body of the closure (where it might be valid for some other usage). By splitting it (semantically the same) into a let and a call, clippy shut up on its own for now. Note that even though the braces are macro syntax and not a block, |
Use macro_rules! let_workaround {
(let $name:ident = $val:expr; $($rest:tt)+) => {
match $val {
$name => {
let_workaround! { $($rest)+ }
}
}
};
($($rest:tt)+) => { $($rest)+ }
} |
There is a workaround for that: macro_rules! select {
($cond:expr, $iftrue:expr, $iffalse:expr) => {
'outer: {
(
'inner: {
if $cond { break 'inner }
break 'outer $iffalse
},
$iftrue
).1
}
}
} To clarify the structure, that's essentially like let tuple = (
if condition { break iftrue } ,
iffalse
);
break tuple.1; or just if condition {
break iftrue
}
break iffalse so In combination with the above variant of let_workaround! {
let ab = select!(a < b, format_args!(" < {b}"),
select!(a > b, format_args!(" > {b}"),
format_args!("")));
let bc = select!(b < c, format_args!(" < {c}"),
select!(b > c, format_args!(" > {c}"),
format_args!("")));
let ca = select!(c < a, format_args!(" < {a}"),
select!(c > a, format_args!(" > {a}"),
format_args!("")));
let min = a.min(b).min(c);
let f_abc = select!(min == a, format_args!("{a}{ab}{bc}{ca}"),
select!(min == b, format_args!("{b}{bc}{ca}{ab}"),
select!(min == c, format_args!("{c}{ca}{ab}{bc}"),
unreachable!())));
write!(f, "{f_abc}")
} https://godbolt.org/z/EcKqKTKGq (This came up in related discussion in https://internals.rust-lang.org/t/format-args-with-long-lifetimes/19494) |
Add known issue of let binding to format_args doc Simply add doc about rust-lang#92698. `@rustbot` label +T-rustdoc -T-libs r? `@GuillaumeGomez`
Rollup merge of rust-lang#111106 - Stargateur:doc/format_args, r=m-ou-se Add known issue of let binding to format_args doc Simply add doc about rust-lang#92698. `@rustbot` label +T-rustdoc -T-libs r? `@GuillaumeGomez`
Thanks for this. Using a closure is the only way I was able to get it to work. |
At the moment, we cannot store the result of a
format_args!
in a value:The list of confused or annoyed users by this is rather long:
I understand if the
format_args!
macro cannot be changed, but then please provide an alternative way of buildingfmt::Arguments
without that restriction. Even a small performance overhead (for example cloning the values) would be an improvement compared to the common workarounds that have to be used otherwise.The text was updated successfully, but these errors were encountered: