问题
Is it possible to get a value from a collection and apply a method to it which accepts only self
and not &self
?
Minimal Working Example
What I would like to write is something akin to:
use std::collections::HashMap;
fn get<B>(key: i32, h: HashMap<i32, Vec<(i32, B)>>) -> i32 where B: Into<i32> {
let v: &Vec<(i32, B)> = h.get(&key).unwrap();
let val: &B = v.first().unwrap().1;
// Do something to be able to call into
// I only need the value as read-only
// Does B have to implement the Clone trait?
return val.into();
}
I have tried in vain to dribble mut
here and there to try to appease compiler error after compiler error, but this is really a fool's errand.
use std::collections::HashMap;
fn get<B>(key: i32, mut h: HashMap<i32, Vec<(i32, B)>>) -> i32 where B: Into<i32> {
let mut v: &Vec<(i32, B)> = h.get_mut(&key).unwrap();
let ref mut val: B = v.first_mut().unwrap().1;
return (*val).into();
}
Is this sort of thing even possible or does B
have to implement the Clone
trait?
I've also tried:
- Unsafe
- Raw pointers
I've not tried:
Box
- Other Rust constructs that I have not encountered, I mention this to explicitly state that I have not omitted any approaches that I know of.
回答1:
Is it possible to get a value from a collection and apply a method to it which accepts only
self
and not&self
?
In general, no, not without removing it from the collection. The collection owns the value. Methods that take self
want to transform the item while consuming the ownership, so you have to transfer ownership.
Cloning or copying an item creates a new item with new ownership that you can then give to the method.
In your particular case, you can almost get away with this exciting where
clause:
where for<'a> &'a B: Into<i32>
Except From<&i32>
is not implemented for i32
. You can write a trait that does what you want though:
use std::collections::HashMap;
trait RefInto<T> {
fn into(&self) -> T;
}
impl RefInto<i32> for i32 {
fn into(&self) -> i32 { *self }
}
fn get<B>(key: i32, h: HashMap<i32, Vec<(i32, B)>>) -> i32
where B: RefInto<i32>
{
let v = h.get(&key).unwrap();
let val = &v.first().unwrap().1;
val.into()
}
// ----
fn main() {
let mut map = HashMap::new();
map.insert(42, vec![(100, 200)]);
let v = get(42, map);
println!("{:?}", v);
}
Alternatively, you might be able to make use of Borrow:
use std::collections::HashMap;
use std::borrow::Borrow;
fn get<B>(key: i32, h: HashMap<i32, Vec<(i32, B)>>) -> i32
where B: Borrow<i32>
{
let v = h.get(&key).unwrap();
let val = &v.first().unwrap().1;
*val.borrow()
}
回答2:
The function consumes the HashMap
. I'm assuming this is your intent and that you therefore don't care about any of its content except the one element you wish to convert into an i32
.
You can use the HashMap::remove
method to extract a value. You can then use Vec::swap_remove
to extract the first element.
use std::collections::HashMap;
fn get<B>(key: i32, mut h: HashMap<i32, Vec<(i32, B)>>) -> i32 where B: Into<i32> {
h.remove(&key)
.unwrap()
.swap_remove(0)
.1
.into()
}
If B
is cheap to copy, then it makes more sense to write a function where it is copied.
The above doesn't handle errors. A version with error handling could look like this:
use std::collections::HashMap;
fn get<B>(key: i32, mut h: HashMap<i32, Vec<(i32, B)>>) -> Option<i32> where B: Into<i32> {
h.remove(&key)
.and_then(|mut vec| {
if vec.is_empty() { None }
else { Some(vec.swap_remove(0).1.into()) }
})
}
Vec::swap_remove
isn't ideal. The functionality of moving an element at an arbitrary index out of the vector without any other work would be handled by the IndexMove
trait which doesn't yet exist though.
来源:https://stackoverflow.com/questions/35873497/getting-value-from-a-collection-without-using-the-clone-trait