如何使用具有关联类型的非静态特征对象?

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&lt;T&gt;(Vec&lt;T&gt;); 中有错误。因为 Vec 不能保存任何 Traits 本身(正如你所说,它们没有大小),所以该声明将变得非法。我用一个盒子做了一个可行的解决方案,但我认为这不是你想要的。 play.rust-lang.org/… @hellow 谢谢!但是,正如您已经猜到的那样,这并不能解决我的问题。我真的需要返回一个特征对象而不是关联类型。对于那些好奇我为什么要这样做的人:我基本上想使用Index 作为特征对象来进行一些类型擦除。喜欢&amp;Index&lt;usize, Output = Display&gt;。这不适用于 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)而不是&amp;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&lt;&amp;Index&lt;usize, Output = Display&gt;&gt;【参考方案3】:

您好,我遇到了和您一样的问题。 “就像&amp;Index&lt;usize, Output = Display&gt;。这不能直接与索引一起使用,但在我的问题中使用索引使它更容易一些。”

我不知道 Rust 是否发布了一些相关功能。但我想出了一个相当愚蠢的方法来满足我的要求。

此方法仅在实现 trait 的结构是可枚举的情况下才有效。假设你有三个结构体Index1, Index2, Index3,它们都实现了特征Index&lt;usize, Output = Display&gt;

然后我们可以简单地包装这些结构体

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

【讨论】:

以上是关于如何使用具有关联类型的非静态特征对象?的主要内容,如果未能解决你的问题,请参考以下文章

面向对象

面向对象三大特征--多态

调用特征的关联函数时如何指定关联类型<Item=...>?

指向任何类类型的非静态成员函数的 C++ 函数指针

对象和类

如何使用具有泛型类型的静态属性并有意义?