我有一个结构Foo
:
struct Foo {
v: String,
// Other data not important for the question
}
我想处理数据流并将结果保存到Vec<Foo>
,并在字段Vec<Foo>
上为此Foo::v
创建索引。
我想使用HashMap<&str, usize>
作为索引,其中键为&Foo::v
,值为Vec<Foo>
中的位置,但我对其他人开放建议。
我希望尽可能快地处理数据流,这需要两次不做明显的事情。
例如,我想:
String
Rc
或RefCell
借阅检查器不允许使用此代码:
let mut l = Vec::<Foo>::new();
{
let mut hash = HashMap::<&str, usize>::new();
//here is loop in real code, like:
//let mut s: String;
//while get_s(&mut s) {
let s = "aaa".to_string();
let idx: usize = match hash.entry(&s) { //a
Occupied(ent) => {
*ent.get()
}
Vacant(ent) => {
l.push(Foo { v: s }); //b
ent.insert(l.len() - 1);
l.len() - 1
}
};
// do something with idx
}
存在多个问题:
hash.entry
借用了密钥,因此s
必须有一个更大的&#34;寿命比hash
s
,而我在第(a)行有一个只读参考那么,在调用String::clone
后,如何在没有额外调用HashMap::get
或致电HashMap::insert
的情况下实施这个简单算法?
答案 0 :(得分:7)
一般来说,你想要完成的是不安全的,Rust正确地阻止你做一些你不应该做的事情。举一个简单的例子,考虑Vec<u8>
。如果向量具有一个项并且容量为1,则向向量添加另一个值将导致重新分配和复制向量中的所有值,从而使对向量的任何引用无效。这会导致索引中的所有键都指向任意内存地址,从而导致不安全的行为。编译器会阻止它。
在这个的情况下,有两个额外的信息,编译器不知道,但程序员不是:
String
是堆分配的,所以将指针移动到该堆分配并不是真正的问题。String
永远不会更改。如果是,那么它可能会重新分配,使引用的地址无效。在这种情况下,只要您正确记录为什么它不安全,就可以使用unsafe
代码。
use std::collections::HashMap;
use std::mem;
#[derive(Debug)]
struct Player {
name: String,
}
fn main() {
let names = ["alice", "bob", "clarice", "danny", "eustice", "frank"];
let mut players = Vec::new();
let mut index = HashMap::new();
for &name in &names {
let player = Player { name: name.into() };
let idx = players.len();
// INSERT REASON WHY THIS CODE IS NOT UNSAFE
let stable_name: &str = unsafe { mem::transmute(&*player.name) };
players.push(player);
index.insert(idx, stable_name);
}
for (k, v) in &index {
println!("{:?} -> {:?}", k, v);
}
for v in &players {
println!("{:?}", v);
}
}
但是,我的猜测是您不希望在main
方法中使用此代码,而是希望从某个函数返回它。这将是一个问题,因为您将很快遇到Why can't I store a value and a reference to that value in the same struct?。
老实说,有些代码风格不符合Rust的限制。如果碰到这些,你可以:
unsafe
代码,最好经过全面测试,并且只展示安全的API。例如,我可能会重写代码以使索引成为密钥的主要所有者:
use std::collections::BTreeMap;
#[derive(Debug)]
struct Player<'a> {
name: &'a str,
data: &'a PlayerData,
}
#[derive(Debug)]
struct PlayerData {
hit_points: u8,
}
#[derive(Debug)]
struct Players(BTreeMap<String, PlayerData>);
impl Players {
fn new<I, S>(iter: I) -> Self
where I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let players = iter.into_iter()
.map(|name| (name.as_ref().to_string(), PlayerData { hit_points: 100 }))
.collect();
Players(players)
}
fn get<'a>(&'a self, name: &'a str) -> Option<Player<'a>> {
self.0.get(name).map(|data| {
Player {
name: name,
data: data,
}
})
}
}
fn main() {
let names = ["alice", "bob", "clarice", "danny", "eustice", "frank"];
let players = Players::new(&names);
for (k, v) in &players.0 {
println!("{:?} -> {:?}", k, v);
}
println!("{:?}", players.get("eustice"));
}
或者,如What's the idiomatic way to make a lookup table which uses field of the item as the key?所示,您可以将您的类型换行并将其存储在set容器中:
use std::collections::BTreeSet;
#[derive(Debug, PartialEq, Eq)]
struct Player {
name: String,
hit_points: u8,
}
#[derive(Debug, Eq)]
struct PlayerByName(Player);
impl PlayerByName {
fn key(&self) -> &str {
&self.0.name
}
}
impl PartialOrd for PlayerByName {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PlayerByName {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.key().cmp(&other.key())
}
}
impl PartialEq for PlayerByName {
fn eq(&self, other: &Self) -> bool {
self.key() == other.key()
}
}
impl std::borrow::Borrow<str> for PlayerByName {
fn borrow(&self) -> &str {
self.key()
}
}
#[derive(Debug)]
struct Players(BTreeSet<PlayerByName>);
impl Players {
fn new<I, S>(iter: I) -> Self
where I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let players = iter.into_iter()
.map(|name| PlayerByName(Player { name: name.as_ref().to_string(), hit_points: 100 }))
.collect();
Players(players)
}
fn get(&self, name: &str) -> Option<&Player> {
self.0.get(name).map(|pbn| &pbn.0)
}
}
fn main() {
let names = ["alice", "bob", "clarice", "danny", "eustice", "frank"];
let players = Players::new(&names);
for player in &players.0 {
println!("{:?}", player.0);
}
println!("{:?}", players.get("eustice"));
}
使用
不会增加运行时间Rc
或RefCell
在不执行分析的情况下猜测性能特征绝不是一个好主意。老实说,我不相信当克隆或删除某个值时递增一个整数会有明显的性能损失。如果问题同时需要索引和向量,那么我会达成某种共享所有权。
答案 1 :(得分:4)
不使用
Rc
或RefCell
来增加运行时间。
@Shepmaster已经证明使用unsafe
完成此操作,一旦你有了,我会鼓励你检查Rc
实际上会花多少钱。以下是Rc
的完整版:
use std::{
collections::{hash_map::Entry, HashMap},
rc::Rc,
};
#[derive(Debug)]
struct Foo {
v: Rc<str>,
}
#[derive(Debug)]
struct Collection {
vec: Vec<Foo>,
index: HashMap<Rc<str>, usize>,
}
impl Foo {
fn new(s: &str) -> Foo {
Foo {
v: s.into(),
}
}
}
impl Collection {
fn new() -> Collection {
Collection {
vec: Vec::new(),
index: HashMap::new(),
}
}
fn insert(&mut self, foo: Foo) {
match self.index.entry(foo.v.clone()) {
Entry::Occupied(o) => panic!(
"Duplicate entry for: {}, {:?} inserted before {:?}",
foo.v,
o.get(),
foo
),
Entry::Vacant(v) => v.insert(self.vec.len()),
};
self.vec.push(foo)
}
}
fn main() {
let mut collection = Collection::new();
for foo in vec![Foo::new("Hello"), Foo::new("World"), Foo::new("Go!")] {
collection.insert(foo)
}
println!("{:?}", collection);
}
答案 2 :(得分:1)
错误是:
error: `s` does not live long enough
--> <anon>:27:5
|
16 | let idx: usize = match hash.entry(&s) { //a
| - borrow occurs here
...
27 | }
| ^ `s` dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
最后的note:
就是答案所在。
s
必须比hash
更长,因为您使用&s
作为HashMap
中的密钥。删除s
时,此引用将无效。但是,正如笔记所说,hash
将在 s
之后删除。快速解决方法是交换声明的顺序:
let s = "aaa".to_string();
let mut hash = HashMap::<&str, usize>::new();
但现在你还有另外一个问题:
error[E0505]: cannot move out of `s` because it is borrowed
--> <anon>:22:33
|
17 | let idx: usize = match hash.entry(&s) { //a
| - borrow of `s` occurs here
...
22 | l.push(Foo { v: s }); //b
| ^ move out of `s` occurs here
这个更明显。 s
借用Entry
,它将一直存在到块的末尾。克隆s
将解决这个问题:
l.push(Foo { v: s.clone() }); //b
我只想分配一次,而不是克隆它
但Foo.v
的类型为String
,因此无论如何它都拥有自己的str
副本。只是那种类型意味着您必须复制s
。
您可以将其替换为&str
,这样就可以将其作为对s
的引用:
struct Foo<'a> {
v: &'a str,
}
pub fn main() {
// s now lives longer than l
let s = "aaa".to_string();
let mut l = Vec::<Foo>::new();
{
let mut hash = HashMap::<&str, usize>::new();
let idx: usize = match hash.entry(&s) {
Occupied(ent) => {
*ent.get()
}
Vacant(ent) => {
l.push(Foo { v: &s });
ent.insert(l.len() - 1);
l.len() - 1
}
};
}
}
请注意,之前我必须将s
的声明移到hash
之前,以便它会比它更长久。但现在,l
包含对s
的引用,因此必须更早地声明它,以便它比l
更长。