How dereference process works?

fn main() {
    let s1 = String::from("hello");

    calculate_length(&s1);
}

fn calculate_length(s: &String) {
    println!("{}", s);
}

(Playground)

What’s the question here?

  1. How string content directly print on the screen
  2. Dereference steps

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.

1 Like

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.