使用借用trait与引用值。

3

我有一个自定义特性,我想要在一个 HashMap<(u32, u32), &'a Edge> 上实现它。由于我希望我的特性能够在所拥有的值和引用值上工作,因此我使用了其他 SO 帖子建议的 Borrow 特性,但是我在处理借用检查器和引用的 &Edge 时遇到了问题。首先,编译器要求我为 &Edge 明确添加生命周期,就像您下面看到的那样。

然而,然后编译器会抱怨函数 get_edge 的返回类型(Option<&'a Edge>)与特性定义的返回类型(Option<&Edge>)不匹配。在我看来,为我的特性定义添加生命周期参数是没有意义的,因此我猜错误一定出现在我对特性的实现上。然而,无论我尝试哪些生命周期参数组合,都没能让编译器满意。我在这里到底做错了什么?

pub struct Edge {
    between: (u32, u32),
    weight: u32,
}

impl Edge {
    pub fn normalize_edge(v1: u32, v2: u32) -> (u32, u32) {
        (v1.min(v2), v1.max(v2))
    }

    fn get_weight(&self) -> u32 {
        self.weight
    }
}

trait EdgeFinder {
    fn get_edge(&self, v1: u32, v2: u32) -> Option<&Edge>;
    fn get_weight(&self, v1: u32, v2: u32) -> Option<u32>;
}

impl<'a, 'b, B: Borrow<HashMap<(u32, u32), &'a Edge>> + 'b> EdgeFinder for B {
    fn get_edge(&self, v1: u32, v2: u32) -> Option<&Edge> {
        self.borrow().get(&Edge::normalize_edge(v1, v2)).map(|&e| e)
    }

    fn get_weight(&self, v1: u32, v2: u32) -> Option<u32> {
        self.get_edge(v1, v2).and_then(|v| Some(v.get_weight()))
    }
}

编辑: 虽然它可能不重要,但我已经添加了Edge的定义。这是编译器的输出:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/graph.rs:44:23
   |
44 |         self.borrow().get(&(0,0)).map(|&e| e)
   |                       ^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the impl at 41:6...
  --> src/graph.rs:41:6
   |
41 | impl<'a, 'b, B: 'b + Borrow<HashMap<(u32, u32), &'a Edge>>> EdgeFinder for B {
   |      ^^
note: ...so that the types are compatible
  --> src/graph.rs:44:23
   |
44 |         self.borrow().get(&(0,0)).map(|&e| e)
   |                       ^^^
   = note: expected `&HashMap<(u32, u32), &Edge>`
              found `&HashMap<(u32, u32), &'a Edge>`
note: but, the lifetime must be valid for the anonymous lifetime defined on the method body at 42:17...
  --> src/graph.rs:42:17
   |
42 |     fn get_edge(&self, v1: u32, v2: u32) -> Option<&Edge> {
   |                 ^^^^^
note: ...so that the expression is assignable
  --> src/graph.rs:44:9
   |
44 |         self.borrow().get(&(0,0)).map(|&e| e)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: expected `Option<&Edge>`
              found `Option<&Edge>`

编辑2: 为了补充一点上下文,我尝试在EdgeFinder周围创建一个包装结构,但我不想强制规定被包装的 EdgeFinder 是拥有者还是引用。

pub struct EdgeViewer<T: EdgeFinder> {
    inner: T,
}

impl<T: EdgeFinder> EdgeViewer<T> {
  //Obviously works for owned values.
  pub fn new(inner: T) -> Self {
     EdgeViewer { inner }
  }
}

然而,当使用引用时,会出现以下编译器输出,这使我相信我需要为引用的版本特别添加一个实现。
error[E0277]: the trait bound `&HashMap<(u32, u32), &Edge>: EdgeFinder` is not satisfied
  --> src/construction.rs:74:43
   |
74 |     let mut edge_viewer = EdgeViewer::new(&edges);
   |                                           -^^^^^
   |                                           |
   |                                           the trait `EdgeFinder` is not implemented for `&HashMap<(u32, u32), &Edge>`
   |                                           help: consider removing the leading `&`-reference
   |
   = help: the following implementations were found:
             <HashMap<(u32, u32), &Edge> as EdgeFinder>
note: required by `EdgeViewer::<T>::new`
  --> src/graph.rs:58:5
   |
58 |     pub fn new(inner: T) -> Self {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^


1
如果您提供边缘结构及其实现以及精确的错误消息,我认为回答会更容易些。 - Jerboas86
1
是的,抱歉,我添加了一些更多的信息。@Jmb 你是对的,然而,在使用特质限定时似乎不起作用。我添加了一些关于我的问题的额外上下文,这显然比我想象的更相关。 - Skyfarmer
1
这段代码片段是否符合您的要求?playground链接 - Jerboas86
1
有一种方法是使用 Borrow,但我并不确定这是否是正确的方法。playground。如果您同意,我可以从中得出答案吗? - Jerboas86
显示剩余4条评论
1个回答

1
完整解决方案:游乐场
use std::{borrow::Borrow, collections::HashMap, marker::PhantomData, ops::Deref};

#[derive(Debug)]
pub struct Edge {
    between: (u32, u32),
    weight: u32,
}

impl Edge {
    pub fn normalize_edge(v1: u32, v2: u32) -> (u32, u32) {
        (v1.min(v2), v1.max(v2))
    }

    fn get_weight(&self) -> u32 {
        self.weight
    }
}

pub trait EdgeFinder {
    fn get_edge(&self, v1: u32, v2: u32) -> Option<&Edge>;
    fn get_weight(&self, v1: u32, v2: u32) -> Option<u32>;
}

impl EdgeFinder for HashMap<(u32, u32), &Edge> {
    fn get_edge(&self, v1: u32, v2: u32) -> Option<&Edge> {
        self.get(&Edge::normalize_edge(v1, v2)).copied()
    }

    fn get_weight(&self, v1: u32, v2: u32) -> Option<u32> {
        self.get_edge(v1, v2).map(|v| v.get_weight())
    }
}

pub struct EdgeViewer<T, U>
where
    T: Borrow<U>,
    U: EdgeFinder,
{
    inner: T,
    __phantom: PhantomData<U>,
}

impl<T, U> EdgeViewer<T, U>
where
    T: Borrow<U>,
    U: EdgeFinder,
{
    pub fn new(inner: T) -> Self {
        EdgeViewer {
            inner,
            __phantom: PhantomData,
        }
    }
}

impl<T, U> Deref for EdgeViewer<T, U>
where
    T: Borrow<U>,
    U: EdgeFinder,
{
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

fn main() {
    let e1 = Edge {
        between: (0, 1),
        weight: 1,
    };

    let e2 = Edge {
        between: (1, 2),
        weight: 1,
    };

    let mut map = HashMap::new();
    map.insert((0, 1), &e1);
    map.insert((1, 2), &e2);

    let viewer: EdgeViewer<_, HashMap<(u32, u32), &Edge>> = EdgeViewer::new(&map);

    let found_edge = viewer.get_edge(0, 1);

    println!("{:?}", found_edge);
}


首先,在 `HashMap<(u32, u32), &Edge>` 上实现了 `EdgeFinder` 特质。
其次,在 `HasMap` 和 `&HasMap` 上实现了 `Borrow>` 特质,因为 `Borrow` 特质提供了对于 `X` 和 `&X` 的 `Borrow` 泛型实现(docs)
因此,`T: Borrow, U: EdgeFinder` 特质约束被 `HashMap<(u32, u32), &Edge>` 和 `&HashMap<(u32, u32), &Edge>` 作为 `T` 满足。
这使得EdgeViewer::new接受HashMap<(u32, u32), &Edge>&HashMap<(u32, u32), &Edge>,并且通过其inner字段使EdgeViewer可以访问EdgeFinder trait。 Deref trait被实现是为了方便,但也可以使用inner的getter。 PhantomData需要移除对U类型的歧义。实际上,Borrow<U>是一个泛型trait,可以在多种类型上实现Borrow。由于有PhantomData,我们可以指定UHashMap<(u32, u32), &Edge>。至少,这是我理解的方式。
注意1:在HashMap<(u32, u32), &'a Edge>和&HashMap<(u32, u32), &'a Edge>上实现EdgeFinder不是一个有效的答案,因为它需要多个EdgeFinder实现。(请参见playground) 注意2:AsRef特性在这里不能轻易使用,因为它没有在HasMap和&HasMap上实现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接