println! 借用还是拥有变量?

91

我对借用和所有权感到困惑。在 Rust 的关于引用和借用的文档中。

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

他们说

println! 可以借用 x

我对此感到困惑。如果 println! 借用了 x,为什么它传递的是 x 而不是 &x

我尝试运行以下代码:

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

这段代码与上面的代码完全相同,只是我将&x传递给了println!。它会在控制台上打印出'6',这是正确的结果,与第一个代码相同。

1个回答

115
print!println!eprint!eprintln!write!writeln!format! 是一种特殊情况,它们隐式地接受要格式化的任何参数的引用。
这些宏不像普通函数和宏那样按照惯例行事,它们悄无声息地接受引用,这是它们与普通函数和宏的区别之一。
fn main() {
    let x = 5;
    println!("{}", x);
}

通过夜间编译器运行 rustc -Z unstable-options --pretty expanded,我们可以看到 println! 展开的结果:
#![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,
                )],
            },
        ));
    };
}

进一步整理,就是这样:
use std::{fmt, io};

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

注意 &x
如果你写 println!("{}", &x),那么你就处理了两层引用;这是因为有一个实现了 std::fmt::Display&T,其中 T 实现了 Display(显示为 impl<'a, T> Display for &'a T where T: Display + ?Sized),它只是将其传递了下去。你也可以写成 &&&&&&&&&&&&&&&&&&&&&&&x

2023年初更新:

  • 自2021年中以来,所需的调用方式为rustc -Zunpretty=expanded,而不是rustc -Zunstable-options --pretty=expanded

  • 自2023-01-28左右(https://github.com/rust-lang/rust/pull/106745),format_args!是AST的一部分,因此println!("{}", x)的展开形式为::std::io::_print(format_args!("{0}\n", x));,不再暴露Arguments::new_v1构造和&x方面。这对于各种原因都是好的(请阅读#106745的描述),但破坏了我在这里清晰演示x只被引用的情况。 (这就是为什么我将其作为附注添加到末尾而不是更新答案的原因-因为它不再起作用。)


2
我不明白为什么你会把这些宏称作“特例”。这种隐式的引用传递对于任何宏都可以实现。 - Markus Unterwaditzer
19
@MarkusUnterwaditzer: 当然,问题是它看起来很正常但实际上并不是。当然,其他宏也可以将自己变成特殊情况。事实是一般情况下强烈建议避免这样做。 - Chris Morgan
11
也许这个可以在书中指出来?这也让我感到困惑。 - mauleros
实际上在 https://doc.rust-lang.org/book/ch05-02-example-structs.html 中提到了,下面说宏 println 只借用,但我仍然想知道为什么,所以我来到这里 :D。@mauleros - david valentino
1
请注意,&x 目前编译出的代码比 x 慢了约 6%。https://stackoverflow.com/questions/76361472/why-formatting-value-vs-ref-generates-different-assembly-in-rust - Yuri Astrakhan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接