fn main() {
let s1 = String::from("hello");
calculate_length(&s1);
}
fn calculate_length(s: &String) {
println!("{}", s);
}
What’s the question here?
- How string content directly print on the screen
- Dereference steps
- Via
String
'sDisplay
implementation, - which delegates its work to the
str
'sDisplay
implementation, - which delegates its work to
Formatter::pad
method, - which, at the end, uses the
<dyn Write>::write_str
method.
I'm unable to trace the execution further, since std::fmt::Write
is implemented non-trivially only on String
, but this should give enough context for now.
Let's look at main
first: s1
is of type String
. When giving &s1
as argument to calculate_length
, then an immutable reference is made (&String
), which is passed to the function.
Now inside function calculate_length
, the reference is passed to a macro println!
(you can see it's a macro because of the exclamation mark). What happens inside a macro can differ, depending on the macro used.
You can click on [src] in the automatic documentation of println!
to see the source:
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(print_internals, format_args_nl)]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ({
$crate::io::_print($crate::format_args_nl!($($arg)*));
})
}
println!
accepts references or owned types. The argument list (tt
stands for TokenTree) is passed as a whole to format_args_nl!
. Not sure where format_args_nl!
is defined though. It seems to be some compiler internal thing.
When trying to understand owned types, references, dereferences, etc., it can make sense to look at examples that do not involve macros.
It’s possible to get the generated code in concrete examples like this for example with the cargo expand
tool. It’s also possible to expand macros in the playground. (Under “tools”.) The concrete code in the OP produces something like
#![feature(fmt_internals)]
#![feature(print_internals)]
fn main() {
let s1 = String::from("hello");
calculate_length(&s1);
}
fn calculate_length(s: &String) {
{
::std::io::_print(::core::fmt::Arguments::new_v1(
&["", "\n"],
&match (&s,) {
_args => [::core::fmt::ArgumentV1::new(
_args.0,
::core::fmt::Display::fmt,
)],
},
));
};
}
In particular, the argument s
is referenced (the &s
) and the result is passed to ArgumentV1::new
together with a function pointer to the corresponding Display::fmt
method. Of course, that function pointer is eventually called on that reference when printing happens; the Display::fmt
in this case where s: &String
(so that &s: &&String
) this is the implementation &String: Display
, which comes from the blanket impl Display for &T where T: Display
together with the impl Display for String
.
I think the dereference (Edit: I mean the dereference of the extra reference introduced by the second &
in &match (&s,)
) ultimately happens in the fmt_refs!
macro:
macro_rules! fmt_refs {
($($tr:ident),*) => {
$(
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized + $tr> $tr for &T {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { $tr::fmt(&**self, f) }
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized + $tr> $tr for &mut T {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { $tr::fmt(&**self, f) }
}
)*
}
}
I think it's the first asterisk in &**self
(the second asterisk dereferences self
, because it's passed as reference).
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.