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

17

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

例如,我应该这样做吗?

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

那么,哪种方法更好,为什么?

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

如果您想通过特征提供两个版本,什么情况下需要这样做?对于这些情况,您如何编写两个函数的基础实现 - 在每个方法签名中重复逻辑还是有一个代理到另一个?哪个代理到哪个,为什么?


我刚刚找到了另一个有用的相关答案,也许对其他人有用: https://dev59.com/xLzpa4cB1Zd3GeqPRsrU - prior
2个回答

18
Rust旨在实现类似于C/C++的性能和语法,但避免了内存问题。为此,它避免使用垃圾回收,而是强制实施“所有权”和“借用”的特定严格内存模型。这些是Rust中至关重要的概念。建议阅读The Rust Book中的理解所有权
内存所有权规则如下: - Rust中每个值都有一个名为所有者的变量。 - 同一时间只能有一个所有者。 - 所有者超出范围时,该值将被删除。
通过强制使用单个所有者,避免了许多C和C++程序中典型的错误和复杂性,同时避免了复杂且运行时速度缓慢的内存管理。
然而,仅有这些还不能满足需求,因此Rust提供了引用。引用使函数可以安全地“借用”数据而不需要拥有所有权。您可以拥有任意数量的不可变引用,也可以拥有只有一个可变引用。
当应用于函数调用时,传递值会将所有权转移到函数。传递引用是“借用”,所有权保留。
理解所有权、借用以及后面的生命周期非常重要。但是这里有一些经验法则。
如果函数需要获取数据的所有权,请按值传递。 如果函数只需要读取数据,请传递引用。 如果函数需要更改数据,请传递可变引用。
请注意,其中没有什么是关于性能的。让编译器来处理它。
假设check仅读取数据并检查其是否正确,则应该传递引用。因此,你的示例将是...
fn foo(val: &Bar) -> bool { check(val) }

在客户端,如果我只有第二个版本但想要使用我的值...
没有理由希望一个函数使用引用来做到这一点。如果函数的工作是管理内存,你就传递它所有权。如果不是,那么它的工作就不是管理你的内存。
也没有必要手动调用 drop。你只需要让变量超出范围,它就会自动被丢弃。
什么时候才需要通过 traits 提供两个版本?
你不需要这样做。如果一个函数可以接受引用,就没有理由接受所有权。

8
如果函数需要所有权,应该按值传递。如果函数只需要引用,则应该按引用传递。当函数不需要按值传递时,通过按值传递 fn foo(val: Bar) 可能需要用户克隆值。在这种情况下,最好通过引用传递,因为可以避免克隆。当函数需要所有权时,通过引用传递 fn foo(val: &Bar) 将要求对其进行复制或克隆。在这种情况下,按值传递更好,因为它使用户控制现有值的所有权是转移还是克隆。函数不必做出那个决定,因此可以避免克隆。有一些例外,例如像i32这样的简单基元类型可以按值传递而无需任何性能惩罚,并且可能更方便。
当您希望通过特征(traits)提供两个版本时,可以使用 Borrow 特征。
fn foo<B: Borrow<Bar>>(val: B) -> bool {
    check(val.borrow())
}

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

什么时候通过Borrow特质同时提供是合适的? - prior
说实话,我还没有看到过这种模式的使用。Borrow 适用于借用值与原始值具有相同行为的情况,请参见文档中的示例。我只将其列为潜在选项。通常,您会使用 AsRef 更通用地接受引用,但是它没有像 Borrow 那样的 T -> T& 实现。也许因为只使用 & 更容易和更清晰。 - kmdreko
你能解释一下“传值可能需要用户克隆值”这句话的意思吗?据我所知,传递值(所有权转移)不需要复制实际值,程序员和语言本身都不需要。该值应该已经保存在堆中,只需要进行一次小的新内存分配即可指向相同的堆位置。这对性能来说应该无关紧要,不是吗? - Pongsakorn Semsuwan
考虑这样一种情况,您需要调用函数并在之后使用该值。如果该函数拥有所有权,则无法在之后使用该值,因此您必须传递一个克隆值。这就是我所说的“可能需要用户克隆该值”的意思。 - kmdreko

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