首页 > 解决方案 > 使用带有引用值的 Borrow trait

问题描述

我有一个要在HashMap<(u32, u32), &'a Edge>. 由于我希望我的特征同时适用于拥有和引用的值,因此我使用Borrow其他 SO 帖子建议的特征,但我在借用检查器和引用的&Edge. 首先,编译器希望我专门添加&Edge我所做的生命周期,如下所示。

但是,编译器会抱怨函数get_edge( Option<&'a Edge>) 的返回类型与特征定义 ( ) 的返回类型不匹配Option<&Edge>。在我看来,在我的 trait 定义中添加生命周期参数是没有意义的,所以我猜这个错误一定是在我的 trait 实现中的某个地方。然而,无论我尝试什么样的生命周期参数组合,我都无法让编译器满意。我在这里到底做错了什么?

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: 为了添加一点上下文,我尝试围绕 an 创建一个包装器结构,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 {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

标签: rustlifetime

解决方案


完整的解决方案:游乐场

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

首先,EdgeFindertrait 在HashMap<(u32, u32), &Edge>.

其次,Borrow<HasMap<K,V>>特征是在 and 上实现的HasMap<K,V>&HasMap<K,V>因为Borrowtrait为and (docs)Borrow<X>提供了全面的实现。X&X

因此,T: Borrow<U>, U: EdgeFindertrait bound 由HashMap<(u32, u32), &Edge>&HashMap<(u32, u32), &Edge>as满足T

这使得EdgeViewer::new接受HashMap<(u32, u32), &Edge>&HashMap<(u32, u32), &Edge>,并通过其字段提供EdgeViewer对特征的访问。EdgeFinderinner

Dereftrait 是为了方便而实现的,但inner也可以使用 getter to。

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

注意 1:EdgeFinder实现 onHashMap<(u32, u32), &'a Edge>并且&HashMap<(u32, u32), &'a Edge>不是有效答案,因为它需要多个EdgeFinder实现。 (见操场)

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


推荐阅读