在将结构插入地图而不重复它时,如何使用struct的成员作为自己的键?

时间:2016-12-08 09:25:09

标签: rust borrow-checker

是否可以在地图中插入一个结构,其中键由所插入的值所拥有?

在C中使用哈希映射时,这是我以前做过的事情。

伪代码示例:

struct MyStruct {
    pub map: BTreeMap<&String, StructThatContainsString>,
    // XXX            ^ Rust wants lifetime specified here!
}

struct StructThatContainsString {
    id: String,
    other_data: u32,
}

fn my_fn() {
    let ms = MyStruct { map: BTreeMap::new() };

    let item = StructThatContainsString {
        id: "Some Key".to_string(),
        other_data: 0,
    }

    ms.insert(&item.id, item);
}

如何正确处理这种情况?

  • 如果这是不可能的,可以反过来做,其中值包含对String的键的引用?

  • 另一种方法是使用set代替map,然后将整个struct存储为关键字,但在比较时仅使用其中一个值< em>(看起来它会起作用,但如果你想比较其他环境中的struct,可能会适得其反)。

2 个答案:

答案 0 :(得分:6)

它不适用于普通参考文献:

let item = StructThatContainsString {
    id: "Some Key".to_string(),
    other_data: 0,
}

ms.insert(&item.id, item);

item移动到地图中,因此不会有任何待处理的借用/引用。

此外,像get_mut()这样的方法会变得危险或不可能,因为它会让你修改具有杰出参考的项目。

假设想要这样做的原因是为了节省空间,显而易见的选择是:

  • 从值struct中取出密钥。如果你同时需要它,你可以在查找地图中的键时获得它,或者迭代器同时包含键和值:

    struct OnlyKey {
        id: String,
    }
    
    struct OnlyValue {
        other_data: u32,
    }
    

    这可以通过适当的方法进行清理,以拆分/重新组合各个部分。

  • 使用Rc之类的内容作为值的关键部分。如果Rc<T>Ord,则BTreeMap会实施Tstruct StructThatContainsString { id: Rc<String>, other_data: u32, } 所需)。

    <asp:LinkButton runat="server" Text='<%# string.Format("{0:N0}", Eval("Amount")) %>'></asp:LinkButton>
    

答案 1 :(得分:3)

使用结构的单个成员作为映射中的键可以通过使用具有零开销包装器结构的集合(原则上)来完成,该结构仅用于覆盖实现。

  • 覆盖Ord, Eq, PartialEq, PartialOrd
    在集合中控制它的顺序。
  • 覆盖Borrow以便BTreeSet.get(..)可以采用用于排序的类型,而不是整个结构。

  • 使用此方法的一个缺点是,在将结构添加到集合中时,需要使用容器包装结构。

这是一个有效的例子:

use ::std::collections::BTreeSet;

#[derive(Debug)]
pub struct MyItem {
    id: String,
    num: i64,
}

mod my_item_ord {
    use super::MyItem;

    #[derive(Debug)]
    pub struct MyItem_Ord(pub MyItem);

    use ::std::cmp::{
        PartialEq,
        Eq,
        Ord,
        Ordering,
    };
    use ::std::borrow::Borrow;

    impl PartialEq for MyItem_Ord {
        fn eq(&self, other: &Self) -> bool {
            return self.0.id.eq(&other.0.id);
        }

    }
    impl PartialOrd for MyItem_Ord {
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
            return self.0.id.partial_cmp(&other.0.id);
        }
    }
    impl Eq for MyItem_Ord {}
    impl Ord for MyItem_Ord {
        fn cmp(&self, other: &Self) -> Ordering {
            return self.0.id.cmp(&other.0.id);
        }
    }
    impl Borrow<str> for MyItem_Ord {
        fn borrow(&self) -> &str {
            return &self.0.id;
        }
    }
}


fn main() {
    use my_item_ord::MyItem_Ord;

    let mut c: BTreeSet<MyItem_Ord> = BTreeSet::new();

    c.insert(MyItem_Ord(MyItem { id: "Zombie".to_string(), num: 21, }));
    c.insert(MyItem_Ord(MyItem { id: "Hello".to_string(), num: 1, }));
    c.insert(MyItem_Ord(MyItem { id: "World".to_string(), num: 22, }));
    c.insert(MyItem_Ord(MyItem { id: "The".to_string(), num: 11,  }));
    c.insert(MyItem_Ord(MyItem { id: "Brown".to_string(), num: 33, }));
    c.insert(MyItem_Ord(MyItem { id: "Fox".to_string(), num: 99, }));

    for i in &c {
        println!("{:?}", i);
    }

    // Typical '.get()', too verbose needs an entire struct.
    println!("lookup: {:?}", c.get(&MyItem_Ord(MyItem { id: "Zombie".to_string(), num: -1, })));
    //                                                                            ^^^^^^^ ignored

    // Fancy '.get()' using only string, allowed because 'Borrow<str>' is implemented.
    println!("lookup: {:?}", c.get("Zombie"));

    println!("done!");
}

为避免必须手动定义,可以将其包装到宏中:

///
/// Macro to create a container type to be used in a 'BTreeSet' or ordered types
/// to behave like a map where a key in the struct is used for the key.
///
/// For example, data in a set may have a unique identifier which
/// can be used in the struct as well as a key for it's use in the set.
///
///
/// ```
/// // Defines 'MyTypeOrd', a container type for existing struct,
/// // using MyType.uuid is used as the key.
/// container_order_by_member_impl(MyTypeOrd, MyType, uuid);
/// ```
///
/// See: http://stackoverflow.com/questions/41035869

#[macro_export]
macro_rules! container_type_order_by_member_struct_impl {
    ($t_ord:ident, $t_base:ty, $t_member:ident) => {
        /// Caller must define the struct, see: container_type_order_by_member_impl
        // pub struct $t_ord(pub $t_base);
        impl PartialEq for $t_ord {
            fn eq(&self, other: &Self) -> bool {
                return (self.0).$t_member.eq(&(other.0).$t_member);
            }
        }
        impl PartialOrd for $t_ord {
            fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
                return (self.0).$t_member.partial_cmp(&(other.0).$t_member);
            }
        }
        impl Eq for $t_ord {}
        impl Ord for $t_ord {
            fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
                return (self.0).$t_member.cmp(&(other.0).$t_member);
            }
        }
        impl ::std::borrow::Borrow<str> for $t_ord {
            fn borrow(&self) -> &str {
                return &(self.0).$t_member;
            }
        }
    }
}

/// Macro that also defines structs.
#[macro_export]
macro_rules! container_type_order_by_member_impl {
    (pub $t_ord:ident, $t_base:ty, $t_member:ident) => {
        pub struct $t_ord(pub $t_base);
        container_type_order_by_member_struct_impl!($t_ord, $t_base, $t_member);
    };
    ($t_ord:ident, $t_base:ty, $t_member:ident) => {
        struct $t_ord(pub $t_base);
        container_type_order_by_member_struct_impl!($t_ord, $t_base, $t_member);
    };
}