使用具有引用值的借用特征

Using Borrow trait with a referenced value

我有一个要在 HashMap<(u32, u32), &'a Edge> 上实现的自定义特征。因为我希望我的特性对拥有的值和引用的值都起作用,所以我按照其他 SO 帖子的建议使用了 Borrow 特性,但是我在借用检查器和引用的 &Edge 方面遇到了问题。首先,编译器希望我专门为 &Edge 添加一个生命周期,正如您在下面看到的那样。

然而,编译器会抱怨函数get_edgeOption<&'a Edge>)的return类型与特征定义的return类型不匹配(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 {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

完整的解决方案:playground

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);
}

首先,EdgeFinder 特性在 HashMap<(u32, u32), &Edge> 上实现。

其次,Borrow<HasMap<K,V>> 特性在 HasMap<K,V>&HasMap<K,V> 上实现,因为 Borrow 特性为 X 提供了 Borrow<X> 一揽子实现和 &X (docs).

因此,T: Borrow<U>, U: EdgeFinder 特征边界满足 HashMap<(u32, u32), &Edge>&HashMap<(u32, u32), &Edge>T

这使得 EdgeViewer::new 接受 HashMap<(u32, u32), &Edge>&HashMap<(u32, u32), &Edge>,并让 EdgeViewer 通过其 inner 字段访问 EdgeFinder 特征。

Deref 特性是为了方便而实现的,但也可以使用 getter 到 inner

需要

PhantomData 来消除 U 类型的歧义。事实上,Borrow<U> 是 U 的通用特征,因此 Borrow 可以在多种类型上实现。感谢 PhantomData,我们可以指定 U 为 HashMap<(u32, u32), &Edge>。至少,我是这样理解的。

注意 1:HashMap<(u32, u32), &'a Edge>&HashMap<(u32, u32), &'a Edge> 上的 EdgeFinder 实施不是有效答案,因为它需要多个 EdgeFinder 实施。 (see playground)

注意2:AsRef trait在这里不能轻易使用,因为它没有在HasMap<K,V>&HasMap<K,V>

上实现