如何使用具有关联类型的非静态特征对象?
Posted
技术标签:
【中文标题】如何使用具有关联类型的非静态特征对象?【英文标题】:How to use non-'static trait objects with associated types? 【发布时间】:2018-11-07 00:38:40 【问题描述】:我有这种类型:
struct Wrap<T>(Vec<T>);
我想实现std::ops::Index
并返回对特征对象的引用。这是我的第一次尝试 (Playground):
use std::ops::Index;
use std::fmt::Display;
impl<T> Index<usize> for Wrap<T>
where
T: Display
type Output = Display;
fn index(&self, index: usize) -> &Self::Output
&self.0[index]
这不起作用并导致此错误:
error[E0310]: the parameter type `T` may not live long enough
--> src/main.rs:13:9
|
7 | impl<T> Index<usize> for Wrap<T>
| - help: consider adding an explicit lifetime bound `T: 'static`...
...
13 | &self.0[index]
| ^^^^^^^^^^^^^^
|
note: ...so that the type `T` will meet its required lifetime bounds
--> src/main.rs:13:9
|
13 | &self.0[index]
| ^^^^^^^^^^^^^^
我想我知道为什么会发生这种情况:type Output = Display
等同于 type Output = Display + 'static
,因为每个 trait 对象都有一个生命周期边界,默认为 'static
。
所以现在我可以添加绑定到我的参数T
的'static
,但我认为这是过度约束。当不使用关联类型时,我可以轻松实现这样的方法:
impl<T> Wrap<T>
where
T: Display,
fn my_index(&self, index: usize) -> &Display
&self.0[index]
不需要'static
绑定,因为现在签名去糖:
fn my_index<'a>(&'a self, index: usize) -> &'a Display + 'a
这是有道理的:特征对象必须至少存在'a
。 (Playground with all the code)。
但是我可以使用关联类型来完成这项工作吗(例如使用 Index
特征)?我觉得这可能适用于泛型关联类型,但是(a)我不确定并且(b)它们还没有实现。
【问题讨论】:
我对生锈的经验并不多,所以不要指望这个。我认为您在struct Wrap<T>(Vec<T>);
中有错误。因为 Vec 不能保存任何 Traits 本身(正如你所说,它们没有大小),所以该声明将变得非法。我用一个盒子做了一个可行的解决方案,但我认为这不是你想要的。 play.rust-lang.org/…
@hellow 谢谢!但是,正如您已经猜到的那样,这并不能解决我的问题。我真的需要返回一个特征对象而不是关联类型。对于那些好奇我为什么要这样做的人:我基本上想使用Index
作为特征对象来进行一些类型擦除。喜欢&Index<usize, Output = Display>
。这不适用于 Index
直接,但在我的问题中使用 Index
会更容易一些。
【参考方案1】:
一种尝试是将生命周期附加到 impl:
// Note: won't work.
impl<'a, T> Index<usize> for Wrap<T>
where
T: Display + 'a,
type Output = Display + 'a;
fn index(&self, index: usize) -> &Self::Output
&self.0[index]
但是,编译器不会接受它,因为没有使用'a
。
error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
--> src/main.rs:7:6
|
7 | impl<'a, T> Index<usize> for Wrap<T>
| ^^ unconstrained lifetime parameter
error code E0207 建议了几种解决方案,但由于我们无法更改 Index
特征,唯一可接受的解决方案是让 Wrap
捕获不受约束的生命周期参数:
use std::ops::Index;
use std::fmt::Display;
use std::marker::PhantomData;
struct Wrap<'a, T>(Vec<T>, PhantomData<&'a ()>);
// ^~ ^~~~~~~~~~~~~~~~~~~
impl<'a, T> Index<usize> for Wrap<'a, T>
where
T: Display + 'a,
type Output = Display + 'a;
fn index(&self, index: usize) -> &Self::Output
&self.0[index]
fn main()
let w = Wrap(vec!['a', 'b'], PhantomData);
println!("", &w[0]); // prints "a"
let s = "hi".to_string();
let w = Wrap(vec![&s], PhantomData);
println!("", &w[0]); // prints "hi"
(Playground)
当然,这会改变你的 API,额外的生命周期会感染所有地方......如果这是不可接受的,你也可以
不要使用Index
trait,而是引入你自己的生命周期敏感特征(因此用户需要使用w.my_index(i)
而不是&w[i]
);或
设置Output = Display + 'static
,排除所有瞬态类型。用户需要克隆或使用Rc
。
【讨论】:
【参考方案2】:返回对 T
的引用,而不是返回对 trait 对象的引用并让用户强制转换为 trait 对象,或者只是让 Rust 从上下文隐式推断何时执行强制转换:
use std::fmt::Display;
use std::ops::Index;
fn main()
let w1 = Wrap(vec!['I', 'b']);
let s = "am".to_string();
let w2 = Wrap(vec![&s]);
let w3 = Wrap(vec![1, 2]);
let mut trait_store: Vec<Box<Display>> = Vec::new();
trait_store.push(Box::new(w1.index(0)));
trait_store.push(Box::new(w2.index(0)));
trait_store.push(Box::new(w3.index(0)));
for el in trait_store
println!("", el);
struct Wrap<T>(Vec<T>);
impl<T> Index<usize> for Wrap<T>
type Output = T;
fn index(&self, index: usize) -> &Self::Output
&self.0[index]
【讨论】:
感谢您的回答!不幸的是,这对我不起作用,因为我想将特征用作特征对象并进行类型擦除。例如,这样就可以存储Vec<&Index<usize, Output = Display>>
。【参考方案3】:
您好,我遇到了和您一样的问题。 “就像&Index<usize, Output = Display>
。这不能直接与索引一起使用,但在我的问题中使用索引使它更容易一些。”
我不知道 Rust 是否发布了一些相关功能。但我想出了一个相当愚蠢的方法来满足我的要求。
此方法仅在实现 trait 的结构是可枚举的情况下才有效。假设你有三个结构体Index1, Index2, Index3
,它们都实现了特征Index<usize, Output = Display>
然后我们可以简单地包装这些结构体
pub enum Indices
Index1(Index1),
Index2(Index2),
Index3(Index3),
然后为枚举及其所有变体实现特征,有一个例子: rust - How do I implement a trait for an enum and its respective variants? - Stack Overflow
【讨论】:
以上是关于如何使用具有关联类型的非静态特征对象?的主要内容,如果未能解决你的问题,请参考以下文章