与复制类型的借用特征相反?

Opposite of Borrow trait for Copy types?

我见过 Borrow 特征用于定义接受拥有类型或引用的函数,例如T&T。然后在函数中调用borrow()方法得到&T.

对于 Copy 类型,是否存在一些允许相反的特征(即接受 T&T 并获得 T 的函数?

例如对于这个例子:

use std::borrow::Borrow;

fn foo<T: Borrow<u32>>(value: T) -> u32 {
    *value.borrow()
}

fn main() {
    println!("{}", foo(&5));
    println!("{}", foo(5));
}

这会调用 borrow() 来获取引用,然后立即取消引用。

如果传入 T 是否有另一个实现只复制值,如果给出 &T 则取消引用?或者上面是写这种东西的惯用方式?

使用您拥有的功能,您只能使用 u32 或可以作为 u32.

借用的类型

您可以使用第二个模板参数使您的函数更通用。

fn foo<T: Copy, N: Borrow<T>>(value: N) -> T {
    *value.borrow()
}

但这只是部分解决方案,因为在某些情况下它需要类型注释才能正常工作。

例如,它开箱即用 usize:

let v = 0usize;
println!("{}", foo(v));

这里编译器猜测foo(v)是一个usize没有问题。

但是,如果您尝试 foo(&v),编译器会抱怨找不到正确的输出类型 T 因为 &T 可以为不同的对象实现多个 Borrow 特征类型。您需要明确指定要将哪一个用作输出。

let output: usize = foo(&v);

Borrow 并没有真正的逆特性,因为它不像 Borrow 那样真正有用作为函数的绑定。原因与所有权有关。

为什么“逆 Borrow”不如 Borrow 有用?

需要引用的函数

考虑一个只需要引用其参数的函数:

fn puts(arg: &str) {
    println!("{}", arg);
}

在这里接受 String 是愚蠢的,因为 puts 不需要取得数据的所有权,但是接受 &str 意味着我们有时可能会强制调用者保留周围的数据比必要的时间长:

{
    let output = create_some_string();
    output.push_str(some_other_string);
    puts(&output);
    // do some other stuff but never use `output` again
} // `output` isn't dropped until here

问题是 output 传递给 puts 后不需要,调用者知道这一点,但是 puts 需要引用,所以 output必须活着直到街区结束。显然,您始终可以通过添加更多块(有时是 let)来在调用者中解决此问题,但是 puts 也可以通用化,让调用者 委托 承担责任清理 output:

fn puts<T: Borrow<str>>(arg: T) {
    println!("{}", arg.borrow());
}

puts 接受 T: Borrow 使调用者可以灵活地决定是保留参数还是将其移动到函数中。¹​​

需要自有值的函数

现在考虑实际需要取得所有权的函数的情况:

struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
    Wrapper(arg)
}

在这种情况下,接受 &str 会很愚蠢,因为 wrap 必须对其调用 to_owned()。如果调用者有一个不再使用的 String ,那将不必要地复制本可以刚刚移动到函数中的数据。在这种情况下,接受 String 是更灵活的选项,因为它允许调用者决定是进行克隆还是传递现有的 String。具有“反向 Borrow”特征不会增加 arg: String 尚未提供的任何灵活性。

但是 String 并不总是最符合人体工程学的参数,因为有几种不同的字符串:&strCow<str>Box<str>...我们可以通过说它接受任何可以转换为 intoString.

来使 wrap 更符合人体工程学
fn wrap<T: Into<String>>(arg: T) -> Wrapper {
    Wrapper(arg.into())
}

这意味着您可以像 wrap("hello, world") 一样调用它,而不必在文字上调用 .to_owned()。这并不是真正的灵活性胜利——调用者总是可以调用.into()而不失一般性——但它是符合人体工程学的 赢了。

Copy 类型呢?

现在,您询问了 Copy 类型。在大多数情况下,上述论点仍然适用。如果你正在写一个函数,像 puts,只需要一个 &A,使用 T: Borrow<A> 对调用者来说可能更灵活;对于像 wrap 这样需要整个 A 的函数,只接受 A 会更灵活。但是对于 Copy 类型,接受 T: Into<A> 人体工程学 优势要小得多 clear-cut.

  • 对于整数类型,因为泛型会干扰类型推断,所以使用它们通常会使 更少 符合使用文字的人体工学;您可能最终不得不显式注释类型。
  • 因为 &u32 没有实现 Into<u32>,所以那个特殊的技巧无论如何在这里都行不通。
  • 由于 Copy 类型很容易作为自有值使用,因此一开始就不太常见地通过引用来使用它们。
  • 最后,当 A: Copy 时将 &A 变成 A 就像添加 * 一样简单;在大多数情况下,能够跳过该步骤可能不足以抵消使用泛型所增加的复杂性。

总而言之,foo 几乎可以肯定只接受 value: u32 并让调用者决定如何获取该值。

另见


¹ 对于这个特定的函数,您可能需要 AsRef<str>,因为您不依赖 Borrow 的额外保证,而且所有 T 都实现了 Borrow<T> 通常与 str 等未确定大小的类型无关。但这不是重点。