4

我有这种类型:

struct Wrap<T>(Vec<T>);

我想实现std::ops::Index并返回对特征对象的引用。这是我的第一次尝试(游乐场):

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.

所以现在我可以将'static绑定添加到我的参数T,但我认为这是过度约束。当不使用关联类型时,我可以轻松实现这样的方法:

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. (带有所有代码的游乐场)。


但是我可以使用关联类型(如Index特征)来完成这项工作吗?我觉得这可能适用于泛型关联类型,但是(a)我不确定并且(b)它们还没有实现。

4

3 回答 3

5

一种尝试是将生命周期附加到 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

错误代码 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"
}

游乐场

当然,这会改变你的 API,额外的生命周期会感染任何地方......如果这是不可接受的,你可以

  • 不使用Indextrait,而是引入你自己的生命周期敏感 trait(因此用户需要使用w.my_index(i)而不是&w[i]);或者
  • 设置Output = Display + 'static并排除所有瞬态类型。用户将需要克隆或使用Rc.
于 2018-05-28T14:59:02.083 回答
0

你好,我遇到了和你一样的问题。“就像&Index<usize, Output = Display>。这不能直接与 Index 一起使用,但在我的问题中使用 Index 让它更容易一些。”

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

此方法仅在实现 trait 的结构是可枚举的情况下才有效。假设你有三个结构Index1, Index2, Index3,它们都实现了 traitIndex<usize, Output = Display>

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

pub enum Indices{
    Index1(Index1),
    Index2(Index2),
    Index3(Index3),
}

然后为枚举及其所有变体实现特征,有一个例子: rust - How do I implement a trait for an enum and its各自的变体?- 堆栈溢出

于 2021-01-23T15:16:18.753 回答
0

返回一个引用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]
    }
}
于 2018-05-28T18:47:28.223 回答