Does println! borrow or own the variable?
Asked Answered
D

1

92

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.

Dodgem answered 26/5, 2015 at 5:48 Comment(0)
L
117

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.)

Landau answered 26/5, 2015 at 6:47 Comment(7)
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.Outshout
@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.Landau
Maybe this could be pointed out in the book? Got me confused as well.Rommel
it is actually mentioned in 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. @RommelWaynant
Note that &x is currently compiling to a ~6% SLOWER code than x. #76361972Saccharometer
Glad you mentioned this pattern is strongly advised against in Rust and that it is limited in the macros; this definitely can't go into normal methods, functions of Rust; it is almost rudimentary for any one who can programme not to silently/implicitly convert between variable and its reference and vice versa, or any kind of silent conversion inside a function, without any warning. And this "feature" should have its own paragraph in "The Book".Solicitous
If you prefer running with cargo, cargo rustc -- -Zunpretty=expanded works as well.Heredes

© 2022 - 2024 — McMap. All rights reserved.