我尝试使用可以实现返回引用或拥有值的方法来定义特征。
类似的东西:
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中表达这一点。
我设法找到了几种方法来使这段代码有效,但我不喜欢其中任何一种:
为特质本身添加显式生命周期:
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
的东西都需要一个明确的生命周期(我认为这本身并不是必需的),而且这个特性在实现时似乎不必要地复杂。
返回可能会或可能不会引用的内容 - 例如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
作为特征对象时,可能会导致一些可忽略的运行时开销。)
另请注意Cow
不好,因为它要求Self::Value
实现ToOwned
(因此,实际上,Clone
),这太强了需求。在没有这些限制的情况下,它很容易实现Cow
的替代方案。
这个问题还有其他解决办法吗?什么是标准/最常见/首选的?
答案 0 :(得分:5)
这可以使用额外的关联对象来解决,以选择是返回类型还是引用,以及一些元编程魔术。
首先,一些助手类型:
dbmodel.findOne(req.params.title, function(err, ) {
records.picture.content = fs.readFileSync(req.files.path);
records.picture.contentType = 'image/png';
records.save(function (err) {
if(!err) {
res.send("Image is saved into MongoDB");
} else {
res.send(400).send();
}
})
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
时会返回T
,而Value
会返回&T
。
然后是特质:
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
需要实现特征以允许我们编写Return
。虽然我们不知道Self的生命周期究竟是什么,但我们可以使用HRTB <Return as ReturnKind<'a, Value>>
使所有生命周期Return
绑定。{/ p>
用法:
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
必须知道此生命周期。自Rust doesn't support associated lifetimes yet以来,不得不像Trait
一样使用它:
Trait<'foo>
但是如果在特征上有生命周期参数就可以了,那么OP已经提供了一个更简单的解决方案:
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
}
答案 1 :(得分:2)
@kennytm提出了一个很好的(如果复杂的)解决方案;我想提出一个更简单的替代方案。
有两种方法可以为值提供生命周期名称:
trait Trait<'a> { ... }
trait Trait { fn f<'a>(&'a self) -> ... }
后者没有得到语言的良好支持,虽然更灵活也更复杂。然而,前者经常就足够了;因此,我没有告诉你:
trait Trait<'a> {
type Value;
fn f(self) -> Self::Value;
}
f
会消耗其输出,如果Self
是不可变参考,那么这是正常的,因为它们是Copy
。
证据在布丁中:
struct Type;
impl Trait<'static> for () {
type Value = Type;
fn f(self) -> Self::Value {
Type
}
}
impl<'a> Trait<'a> for &'a (Type,) {
type Value = &'a Type;
fn f(self) -> Self::Value {
&self.0
}
}
可以毫无问题地调用它:
fn main(){
().f();
(Type,).f();
}
这个解决方案肯定不那么灵活;但它也明显更简单。