在Rust语言中,符号'&'和'*'分别代表什么意思?

91

尽管已经彻底阅读了文档,但我对Rust中的&*符号的含义以及Rust参考究竟是什么还感到困惑。

在这个例子中,它似乎类似于C++引用(也就是在使用时自动取消引用的地址):

fn main() {
    let c: i32 = 5;
    let rc = &c;
    let next = rc + 1;
    println!("{}", next); // 6
}

然而,以下代码的功能完全相同:

fn main() {
    let c: i32 = 5;
    let rc = &c;
    let next = *rc + 1;
    println!("{}", next); // 6
}

在C++中,使用*来取消引用并不正确。因此,我想了解为什么在Rust中这样做是正确的。

我的理解是,在Rust中,在引用前加上*可以取消引用,但*会自动插入,因此您不需要添加它(而在C++中,它会自动插入,如果您插入它,编译时会出错)。

然而,像下面这样的代码将无法编译:

fn main() {
    let mut c: i32 = 5;
    let mut next: i32 = 0;
    {
        let rc = &mut c;
        next = rc + 1;
    }
    println!("{}", next);
}
error[E0369]: binary operation `+` cannot be applied to type `&mut i32`
 --> src/main.rs:6:16
  |
6 |         next = rc + 1;
  |                ^^^^^^
  |
  = note: this is a reference to a type that `+` can be applied to; you need to dereference this variable once for this operation to work
  = note: an implementation of `std::ops::Add` might be missing for `&mut i32`

但这个有效:

fn main() {
    let mut c: i32 = 5;
    let mut next: i32 = 0;
    {
        let rc = &mut c;
        next = *rc + 1;
    }
    println!("{}", next);  // 6
}
似乎对于不可变引用,隐式解引用(类似于C ++)是正确的,但对于可变引用则不是。为什么会这样?
3个回答

65
在C++中,使用*解引用引用是不正确的。因此我想了解为什么在Rust中这样做是正确的。 在Rust中,引用与C++中的引用不同。Rust的引用在用法上更接近于C++的指针(但语义不同)。就内存表示而言,Rust的引用通常只是一个单指针,而C++的引用应该是同一对象的替代名称(因此没有内存表示)。 C++指针和Rust引用之间的区别在于,Rust的引用永远不会为NULL,永远不会未初始化,也永远不会悬空。

Add特性已经实现(请参见文档页面底部),适用于以下配对和所有其他数字原语:

  • &i32 + i32
  • i32 + &i32
  • &i32 + &i32

这只是标准库开发人员实现的一种便利。编译器可以推断出&mut i32可以在任何&i32可以使用的地方使用,但是这对于泛型来说不起作用(还没有?),因此标准库开发人员还需要为以下组合(以及所有原始类型的组合)实现Add特性:

  • &mut i32 + i32
  • i32 + &mut i32
  • &mut i32 + &mut i32
  • &mut i32 + &i32
  • &i32 + &mut i32

正如您所看到的,这可能会变得非常混乱。我相信这种情况将来会消失。在那之前,请注意很少有机会以数学表达式使用&mut i32


4
谢谢,我现在明白了。其实,我之前也想到过可能是这样的,但我却把这个想法给否定了,认为用一个整数的地址来定义整数的总和非常奇怪。也许应该在文档中强调 Rust 引用应该被视为地址,而不应与 C++ 引用混淆。再次感谢您详细的解释。 - John Smith Optional

49

这篇回答是为那些想要了解基础知识的人准备的(例如从Google搜索过来的人)。

来自Rust书中的引用和借用章节:

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

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

These ampersands represent references, and they allow you to refer to some value without taking ownership of it [i.e. borrowing].

The opposite of referencing by using & is dereferencing, which is accomplished with the dereference operator, *.

一个基本的例子:

let x = 5;
let y = &x; //set y to a reference to x

assert_eq!(5, x);
assert_eq!(5, *y); // dereference y

如果我们尝试写assert_eq!(5, y);,我们会得到一个编译错误can't compare `{integer}` with `&{integer}`。(您可以在智能指针章节中阅读更多内容。)并且从方法语法中:

Rust has a feature called automatic referencing and dereferencing. Calling methods is one of the few places in Rust that has this behavior.

Here’s how it works: when you call a method with object.something(), Rust automatically adds in &, &mut, or * so object matches the signature of the method. In other words, the following are the same:

p1.distance(&p2);
(&p1).distance(&p2);

1
我实际上是从谷歌过来的,特别是为了澄清你从“方法语法”引用的段落。我的初步阅读让我相信将p1.distance(&p2)等同于(&p1).distance(&p2)一定是一个打字错误,并且第二次调用distance应该是(*p1).distance(&p2)。经过再思考,我得出结论,这并不是打字错误。Rust正在将p1.的类型与distance的第一个参数匹配。这是书中适当的阅读方式吗? - HandsomeGorilla
2
是的,没有错别字。如果你用“distance”的“第一个参数”指的是“self”,那么是的。 - mb21
1
这正是我想表达的,谢谢。 - HandsomeGorilla

5

std::ops::Add的文档中可以得知:

impl<'a, 'b> Add<&'a i32> for &'b i32
impl<'a> Add<&'a i32> for i32
impl<'a> Add<i32> for &'a i32
impl Add<i32> for i32

看起来对于数字,二进制加号运算符是实现在操作数的共享(但不可变)引用和操作数的拥有版本的组合上的。这与自动解引用无关。


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