【问题标题】:How to decide when function input params should be references or not?如何决定函数输入参数何时应该是引用?
【发布时间】:2020-11-13 00:32:10
【问题描述】:

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

例如,我应该这样做吗?

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 ownership borrowing


【解决方案1】:

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

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

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

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


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

你可以使用Borrow trait:

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

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

【讨论】:

  • 什么时候通过Borrow trait 提供这两种方法比较合适?
  • 说实话,我还没有看到这种模式在任何地方使用过。 Borrow 适用于借用值与原始值具有相同 行为 的情况,请参阅文档中的 examples。我只是把它列为一个潜在的选择。通常你会使用AsRef 来更一般地接受引用,但是它没有T -> T&amp; 像Borrow 那样的实现。可能是因为使用&amp; 更容易更清晰。
【解决方案2】:

Rust 的目标是在没有内存问题的情况下获得类似于 C/C++ 的性能和语法。为此,它避免了诸如garbage collection 之类的事情,而是强制执行“所有权”和“借用”的特定严格内存模型。这些是 Rust 中的关键概念。我建议阅读 The Rust Book 中的Understanding Ownership。

内存所有权的规则是……

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

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

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


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

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

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

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

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

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-18
    • 2013-07-28
    • 2019-12-30
    • 2020-12-30
    • 2020-03-22
    • 2014-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多