如何决定函数输入参数何时应该是引用?

How to decide when function input params should be references or not?

写函数时,如何判断传入参数是引用还是消费?

例如,我应该这样做吗?

fn foo(val: Bar) -> bool { check(val) } // version 1

或者改用引用参数?

fn foo(val: &Bar) -> bool { check(*val) } // version 2

在客户端,如果我只有第二个版本但想消耗我的价值,我必须做这样的事情:

// given in: Bar
let out = foo(&in); // using version 2 but wanting to consume ownership
drop(in);

另一方面,如果我只有第一个版本但想保留我的参考,我必须这样做:

// given in: &Bar
let out = foo(in.clone()); // using version 1 but wanting to keep reference alive

那么哪个是首选,为什么?

做出这个选择是否有任何性能方面的考虑?或者编译器是否使它们在性能方面等效,如何实现?

你什么时候想提供这两个版本(通过特征)?在那些时候,您如何为这两个函数编写底层实现——您是在每个方法签名中复制逻辑,还是您有一个代理到另一个?哪个到哪个,为什么?

Rust 的目标是在没有内存问题的情况下获得与 C/C++ 相似的性能和语法。为此,它避免了 The Rust Book 中的 garbage collection and instead enforces a particular strict memory model of "ownership" and "borrowing". These are critical concepts in Rust. I would suggest reading Understanding Ownership 之类的事情。

内存所有权的规则是...

  • Rust 中的每个值都有一个称为其所有者的变量。
  • 一次只能有一个所有者。
  • 当所有者超出范围时,该值将被删除。

强制单一所有者避免了 C 和 C++ 程序典型的许多错误和并发症,同时避免了运行时复杂和缓慢的内存管理。

你不能仅靠它走得太远,所以 Rust 提供了参考。引用允许函数安全地“借用”数据而无需取得所有权。您可以根据需要或者个不可变引用,只有一个可变引用。


当应用于函数调用时,传递值会将所有权传递给函数。传递引用是“借用”,保留所有权。

了解所有权、借贷以及以后的 lifetimes 真的非常重要。但这里有一些经验法则。

  • 如果您的函数需要获取数据的所有权,请按值传递。
  • 如果你的函数只需要读取数据,传递一个引用。
  • 如果您的函数需要更改数据,请传递可变引用。

注意其中没有的内容:性能。让编译器来处理。

假设check只是读取数据,检查没问题,应该可以参考一下。所以你的例子是...

fn foo(val: &Bar) -> bool { check(val) }

On the client side, if I only had the second version but wanted to have my value consumed...

没有理由需要一个需要引用的函数来做到这一点。如果管理内存是函数的工作,则将所有权传递给它。如果不是,则管理您的内存不是它的工作。

也不需要手动调用drop。您只需让变量超出范围,它就会自动删除。

And when would you want to offer both versions (via traits)?

你不会。如果一个函数可以获取引用,那么它就没有理由获取所有权。

如果函数需要所有权,您应该按值传递。如果函数只需要一个引用,你应该通过引用传递。

当函数运行不需要时按值 fn foo(val: Bar) 传递可能需要用户克隆该值。在这种情况下,首选通过引用传递,因为可以避免克隆。

当函数需要所有权时,通过引用传递 fn foo(val: &Bar) 将要求它复制或克隆该值。在这种情况下,按值传递是首选,因为它让用户可以控制现有值的所有权是转移还是克隆。该函数不必做出该决定,并且可以避免克隆。

有一些例外,像 i32 这样的简单原语可以按值传递而不会造成任何性能损失,并且可能更方便。


And when would you want to offer both versions (via traits)?

您可以使用 Borrow 特征:

fn foo<B: Borrow<Bar>>(val: B) -> bool {
    check(val.borrow())
}

let b: Bar = ...;
foo(&b); // both of
foo(b);  // these work