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

pri*_*ior 20 ownership rust borrowing

在编写函数时,如何决定是引用还是使用输入参数?

例如,我应该这样做吗?

fn foo(val: Bar) -> bool { check(val) } // version 1
Run Code Online (Sandbox Code Playgroud)

或者使用引用参数代替?

fn foo(val: &Bar) -> bool { check(*val) } // version 2
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

Sch*_*ern 21

Rust 的目标是拥有与 C/C++ 类似的性能和语法,而没有内存问题。为此,它避免了垃圾收集之类的事情,而是强制执行特定的严格的“所有权”和“借用”内存模型。这些是 Rust 中的关键概念。我建议阅读《The Rust Book》中的《理解所有权》

\n

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

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

强制执行单一所有者可以避免 C 和 C++ 程序典型的许多错误和复杂性,同时避免运行时复杂且缓慢的内存管理。

\n

仅此无法走得太远,因此 Rust 提供了参考。引用可以让函数安全地“借用”数据,而无需获取所有权。您可以拥有任意数量的不可变引用,可以仅拥有一个可变引用。

\n
\n

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

\n

了解所有权、借用以及随后的生命周期真的非常非常重要。但这里有一些经验法则。

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

请注意其中不包含的内容:性能。让编译器来处理这个问题。

\n

假设check只读取数据并检查是否正常,则应该采用引用。所以你的例子是......

\n
fn foo(val: &Bar) -> bool { check(val) }\n
Run Code Online (Sandbox Code Playgroud)\n
\n

在客户端,如果我只有第二个版本但想要消耗我的价值......

\n
\n

没有理由需要一个需要引用的函数来做到这一点。如果该函数的工作是管理内存,则将其所有权传递给它。如果不是,那么管理你的记忆就不是它的工作了。

\n

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

\n
\n

您什么时候想提供两个版本(通过特征)?

\n
\n

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

\n


kmd*_*eko 8

如果函数需要所有权,则应该按值传递。如果函数只需要引用,则应按引用传递。

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

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

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


您什么时候想提供两个版本(通过特征)?

您可以使用该Borrow特征:

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

let b: Bar = ...;
foo(&b); // both of
foo(b);  // these work
Run Code Online (Sandbox Code Playgroud)