问题描述
我正在尝试使用一种方法来定义特征,该方法可以实现为返回引用或拥有的值.
I'm trying to define a trait with a method that can be implemented to either return a reference or an owned value.
类似于:
struct Type;
trait Trait {
type Value;
fn f(&self) -> Self::Value;
}
impl Trait for () {
type Value = Type;
fn f(&self) -> Self::Value {
Type
}
}
impl Trait for (Type,) {
type Value = &Type; // error[E0106]: missing lifetime specifier
fn f(&self) -> Self::Value {
&self.0
}
}
但是这段代码不起作用,因为 &Type
缺少生命周期说明符.我希望 &Type
与 &self
具有相同的生命周期(即 fn f<'a>(&'a self) -> &'a Type
),但我不知道如何在 Rust 中表达.
This piece of code doesn't work though, since &Type
is missing a lifetime specifier. I'd want &Type
to have the same lifetime as &self
(i.e. fn f<'a>(&'a self) -> &'a Type
), but I don't know how to express this in Rust.
我设法找到了几种方法来使这段代码工作,但我不喜欢它们中的任何一种:
I managed to find a couple of ways to make this code work, but I don't love either of them:
为特征本身添加一个明确的生命周期:
Adding an explicit lifetime to the trait itself:
trait Trait<'a> {
type Value;
fn f<'b>(&'b self) -> Self::Value where 'b: 'a;
}
impl<'a> Trait<'a> for () {
type Value = Type;
fn f<'b>(&'b self) -> Self::Value
where 'b: 'a
{
Type
}
}
impl<'a> Trait<'a> for (Type,) {
type Value = &'a Type;
fn f<'b>(&'b self) -> Self::Value
where 'b: 'a
{
&self.0
}
}
我不喜欢这个解决方案的是,任何使用 Trait
的东西都需要一个明确的生命周期(我认为这不是本质上必要的),而且这个 trait 实现起来似乎不必要地复杂.
What I don't like of this solution is that anything using Trait
needs an explicit lifetime (which I believe is not intrinsically necessary), plus the trait seems unnecessarily complicated to implement.
返回一些可能是也可能不是参考的东西 - 比如 std::borrow::Cow
:
Returning something that might or might not be a reference - like std::borrow::Cow
:
trait Trait {
type Value;
fn f<'a>(&'a self) -> Cow<'a, Self::Value>;
}
impl Trait for () {
type Value = Type;
fn f<'a>(&'a self) -> Cow<'a, Self::Value> {
Cow::Owned(Type)
}
}
impl Trait for (Type,) {
type Value = Type;
fn f<'a>(&'a self) -> Cow<'a, Self::Value> {
Cow::Borrowed(&self.0)
}
}
我不喜欢这个解决方案的是 ().f()
是一个 Cow
:我需要调用 ().f().into_owned()
获取我的Type
.这似乎是不必要的(并且在使用 Trait
作为 trait 对象时可能会导致一些可以忽略不计的运行时开销).
What I don't like of this solution is that ().f()
is a Cow<_>
: I'd need to call ().f().into_owned()
to obtain my Type
. That seems unnecessary (and might result in some negligible run-time overhead when using Trait
as a trait object).
这个问题还有其他解决方案吗?什么是标准的/最常见的/首选的?
Are there any other solutions to this problem? What's the standard/most common/preferred one?
推荐答案
这可以通过使用额外的关联对象来解决,以在返回类型或引用之间进行选择,再加上一些元编程魔法.
This could be solved using an additional associated object to choose between whether to return a type or a reference, plus some meta-programming magic.
首先,一些辅助类型:
struct Value;
struct Reference;
trait ReturnKind<'a, T: ?Sized + 'a> {
type Type: ?Sized;
}
impl<'a, T: ?Sized + 'a> ReturnKind<'a, T> for Value {
type Type = T;
}
impl<'a, T: ?Sized + 'a> ReturnKind<'a, T> for Reference {
type Type = &'a T;
}
ReturnKind
是一个类型级函数",当输入"为 Value
时返回 T
,&
.参考
的T
ReturnKind
is a "type-level function" which returns T
when the "input" is Value
, and &T
for Reference
.
然后是特征:
trait Trait {
type Value;
type Return: for<'a> ReturnKind<'a, Self::Value>;
fn f<'a>(&'a self) -> <Self::Return as ReturnKind<'a, Self::Value>>::Type;
}
我们通过调用"类型级函数ReturnKind
来产生返回类型.
We produce the return type by "calling" the type-level function ReturnKind
.
输入参数"Return
需要实现 trait 以允许我们编写 >
.虽然我们不知道 Self 的生命周期究竟是什么,但我们可以使用 返回:for<'a>ReturnKind
.
The "input argument" Return
needs to implement the trait to allow us to write <Return as ReturnKind<'a, Value>>
. Although we don't know what exactly the lifetime Self will be, we could make Return
bound by all possible lifetime using HRTB Return: for<'a> ReturnKind<'a, Value>
.
用法:
impl Trait for () {
type Value = f64;
type Return = Value;
fn f(&self) -> f64 {
42.0
}
}
impl Trait for (f64,) {
type Value = f64;
type Return = Reference;
fn f(&self) -> &f64 {
&self.0
}
}
fn main() {
let a: (f64,) = ( ().f(), );
let b: &f64 = a.f();
println!("{:?} {:?}", a, b);
// (42,) 42
}
请注意,以上仅在 Value
类型具有 'static
生命周期时才有效.如果 Value
本身有一个有限的生命周期,那么这个生命周期必须通过 Trait
知道.由于 Rust 尚不支持关联生命周期,不幸的是,它必须像 Trait
一样使用:
Note that the above only works when the Value
type has 'static
lifetime. If the Value
itself has a limited lifetime, this lifetime has to be known by the Trait
. Since Rust doesn't support associated lifetimes yet, it has to be used like Trait<'foo>
, unfortunately:
struct Value;
struct Reference;
struct ExternalReference;
trait ReturnKind<'a, 's, T: ?Sized + 'a + 's> {
type Type: ?Sized;
}
impl<'a, 's, T: ?Sized + 'a + 's> ReturnKind<'a, 's, T> for Value {
type Type = T;
}
impl<'a, 's, T: ?Sized + 'a + 's> ReturnKind<'a, 's, T> for Reference {
type Type = &'a T;
}
impl<'a, 's, T: ?Sized + 'a + 's> ReturnKind<'a, 's, T> for ExternalReference {
type Type = &'s T;
}
trait Trait<'s> {
type Value: 's;
type Return: for<'a> ReturnKind<'a, 's, Self::Value>;
fn f<'a>(&'a self) -> <Self::Return as ReturnKind<'a, 's, Self::Value>>::Type;
}
impl Trait<'static> for () {
type Value = f64;
type Return = Value;
fn f(&self) -> f64 {
42.0
}
}
impl Trait<'static> for (f64,) {
type Value = f64;
type Return = Reference;
fn f(&self) -> &f64 {
&self.0
}
}
impl<'a> Trait<'a> for (&'a f64,) {
type Value = f64;
type Return = ExternalReference;
fn f(&self) -> &'a f64 {
self.0
}
}
fn main() {
let a: (f64,) = ( ().f(), );
let b: &f64 = a.f();
let c: &f64 = (b,).f();
println!("{:?} {:?} {:?}", a, b, c);
// (42,) 42 42
}
但是如果在特征上设置生命周期参数没问题,那么 OP 已经提供了一个更简单的解决方案:
But if having the lifetime parameter on the trait is fine, then OP already provided an easier solution:
trait Trait<'a> {
type Value;
fn f<'b>(&'b self) -> Self::Value where 'b: 'a;
}
impl<'a> Trait<'a> for () {
type Value = f64;
fn f<'b: 'a>(&'b self) -> Self::Value {
42.0
}
}
impl<'a> Trait<'a> for (f64,) {
type Value = &'a f64;
fn f<'b: 'a>(&'b self) -> Self::Value {
&self.0
}
}
impl<'a, 's> Trait<'s> for (&'a f64,) {
type Value = &'a f64;
fn f<'b: 's>(&'b self) -> Self::Value {
self.0
}
}
fn main() {
let a: (f64,) = ( ().f(), );
let b: &f64 = a.f();
let c: &f64 = (b,).f();
println!("{:?} {:?} {:?}", a, b, c);
// (42,) 42 42
}
这篇关于可以实现以返回引用或拥有的值的特征方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!