与复制类型的借用特征相反?
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
并不总是最符合人体工程学的参数,因为有几种不同的字符串:&str
、Cow<str>
、Box<str>
...我们可以通过说它接受任何可以转换为 into
和 String
.
来使 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
等未确定大小的类型无关。但这不是重点。
我见过 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
并不总是最符合人体工程学的参数,因为有几种不同的字符串:&str
、Cow<str>
、Box<str>
...我们可以通过说它接受任何可以转换为 into
和 String
.
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
等未确定大小的类型无关。但这不是重点。