90

I am confused with borrowing and ownership. In the Rust documentation about reference and borrowing

let mut x = 5;
{
    let y = &mut x;
    *y += 1;
}
println!("{}", x);

They say

println! can borrow x.

I am confused by this. If println! borrows x, why does it pass x not &x?

I try to run this code below

fn main() {
    let mut x = 5;
    {
        let y = &mut x;
        *y += 1;
    }
    println!("{}", &x);
}

This code is identical with the code above except I pass &x to println!. It prints '6' to the console which is correct and is the same result as the first code.

Kim Kern
  • 54,283
  • 17
  • 197
  • 195
kevinyu
  • 1,367
  • 10
  • 12

1 Answers1

113

The macros print!, println!, eprint!, eprintln!, write!, writeln! and format! are a special case and implicitly take a reference to any arguments to be formatted.

These macros do not behave as normal functions and macros do for reasons of convenience; the fact that they take references silently is part of that difference.

fn main() {
    let x = 5;
    println!("{}", x);
}

Run it through rustc -Z unstable-options --pretty expanded on the nightly compiler and we can see what println! expands to:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
fn main() {
    let x = 5;
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["", "\n"],
            &match (&x,) {
                (arg0,) => [::core::fmt::ArgumentV1::new(
                    arg0,
                    ::core::fmt::Display::fmt,
                )],
            },
        ));
    };
}

Tidied further, it’s this:

use std::{fmt, io};

fn main() {
    let x = 5;
    io::_print(fmt::Arguments::new_v1(
        &["", "\n"],
        &[fmt::ArgumentV1::new(&x, fmt::Display::fmt)],
        //                     ^^
    ));
}

Note the &x.

If you write println!("{}", &x), you are then dealing with two levels of references; this has the same result because there is an implementation of std::fmt::Display for &T where T implements Display (shown as impl<'a, T> Display for &'a T where T: Display + ?Sized) which just passes it through. You could just as well write &&&&&&&&&&&&&&&&&&&&&&&x.


Early 2023 update:

  • Since mid-2021, the required invocation has been rustc -Zunpretty=expanded rather than rustc -Zunstable-options --pretty=expanded.

  • Since 2023-01-28 or so (https://github.com/rust-lang/rust/pull/106745), format_args! is part of the AST, and so the expansion of println!("{}", x) is ::std::io::_print(format_args!("{0}\n", x));, not exposing the Arguments::new_v1 construction and &x aspects. This is good for various reasons (read #106745’s description), but ruins my clear demonstration here that x was only taken by reference. (This is why I’ve added this as a note at the end rather than updating the answer—since it no longer works.)

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • 2
    I don't understand why you'd call those macros a "special case". This kind of implicit reference-passing can be implemented for any macro. – Markus Unterwaditzer Nov 29 '15 at 13:06
  • 19
    @MarkusUnterwaditzer: Sure, but the thing is that it looks normal but isn’t. And sure, other macros can make themselves special cases too. The fact is that it’s strongly advised against in general. – Chris Morgan Dec 03 '15 at 12:55
  • 11
    Maybe this could be pointed out in the book? Got me confused as well. – mauleros Oct 16 '20 at 02:02
  • it is actually mentioned in https://doc.rust-lang.org/book/ch05-02-example-structs.html, down there saying the macro println only borrow, but i still wonder thats why i cam here :D. @mauleros – david valentino Jan 14 '23 at 03:47
  • Note that `&x` is currently compiling to a ~6% SLOWER code than `x`. https://stackoverflow.com/questions/76361472/why-formatting-value-vs-ref-generates-different-assembly-in-rust – Yuri Astrakhan Jun 01 '23 at 01:47