I've been confused about println! for a while. I can pass it a value, or a reference, and it does not seem to care, there are no compiler complaints. Eventually I realized, its a macro not a function, so I'm not passing it something like I pass something to a function. Going deeper, the code that println! expands to must also avoid taking ownership, or maybe the compiler changes a value to a reference?
I'm not sure what the question is. In particular, I don't understand what you mean by this:
It doesn't.
The expansion of println!()
always, unconditionally takes a reference that points to the value to be formatted, in order not to consume it.
It's fine to explicitly pass a reference to println!()
– Rust's type system is very regular, you can have a reference to any other type, including a reference-to-reference.
Moreover, Display
and other formatting traits are transparently implemented for references, so passing a reference just formats the underlying value.
You can think of macros as special functions that take Rust code as input and output Rust code before the rest of your code is compiled. Thinking of it like this, you're not really passing in values or references to println, just bits of code that it may use in the code it outputs. For example:
macro_rules! print_string_macro {
($i: ident) => {
print_string(&$i)
}
}
fn print_string(s: &str) {
println!("{s}");
}
fn main() {
let s = String::from("hello, world!");
print_string_macro!(s);
print_string_macro!(s);
print_string_macro!(s);
print_string_macro!(s);
}
Here, print_string_macro
is just taking in the identifier s
(not s
itself) and then generating the code print_string(&s)
. As far as the borrow checker is concerned, the main function is completely equivalent to
fn main() {
let s = String::from("hello, world!");
print_string(&s);
print_string(&s);
print_string(&s);
print_string(&s);
}
I was going to explain how examining the macro expansion result could help but TIL format_args
no longer expands to Rust code… which makes is harder to explain I suppose
As far as I remember, previously, it did expand to some code that simply involved the expressions &EXPR
for any EXPR
passed to println!
, so … fairly straightforward, just taking some references.
Yes, println!
takes references behind the scenes. An arg
becomes something like &arg as &dyn Display
.
I believe it was changed in this PR, in January. The reasoning is to enable optimizations of nested string literals and format_args!
calls.
Not that there was much use in the old expansion anyway. It was perma-unstable and would just pollute the expanded code, causing compile errors if you tried to build it. There are still perma-unstable features in expanded code, but at least it's one less thing to clean up.
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.