Convert Option<RefCell<T>> to Option<&T> - rust

We know that it's easy to convert a value of type RefCell<T> to a value of type &T for use as a parameter in a function:
fn main() {
let a: RefCell<i32> = RefCell::new(0);
my_func(a.borrow().deref());
}
fn my_func(i: &i32) {}
In my scenario, the RefCells are stored in a HashMap, so they get obtained wrapped in an Option. I also want the function I pass them to have the notion of option, but I only want to pass the non-mutable reference, not the whole RefCell. We can achieve this like so:
fn main() {
let a: Option<RefCell<i32>> = Some(RefCell::new(0));
match a {
Some(ref_cell) => my_func(Some(ref_cell.borrow().deref())),
None => my_func(None)
};
}
fn my_func(i: Option<&i32>) {}
This works, but in my specific scenario, my_func takes several of these Option<&T>s as a parameter, so doing it this way means the match just gets nested for each parameter and grows exponentially. It would therefore be helpful to have someway of doing this:
fn main() {
let a: Option<RefCell<i32>> = Some(RefCell::new(0));
let c = match a {
Some(ref_cell) => Some(ref_cell.borrow().deref()), // won't compile as this borrow won't live long enough
None => None
};
my_func(c);
}
fn my_func(i: Option<&i32>) {}
So essentially I want to be able to convert from Option<RefCell<T>> to Option<&T>. I feel like this should be possible somehow but I can't figure out a way to do it. I always run into some issue of performing the .borrow() on the RefCell but it not living long enough.

You can do this using the methods on Option:
a.as_ref().map(RefCell::borrow).as_deref()
as_ref() is used to convert the Option<RefCell<_>> into a Option<&RefCell<_>> to avoid consuming it. If you already have a Option<&RefCell<_>> because you got it from hash_map.get() or similar, then you can skip this.
map(RefCell::borrow) is used to call .borrow() on the value if it exists. This will create a Option<Ref<'_, _>>.
as_deref() is the equivalent of calling .deref() on the value if it exists.
It is important to do it this way instead of trying to merge the .borrow() and .deref() in a single .map() call because this keeps the intermediate Ref<'_, _> value alive.
a.as_ref().map(|a| a.borrow().deref())
error[E0515]: cannot return reference to temporary value
--> src/main.rs:8:24
|
8 | a.as_ref().map(|a| a.borrow().deref())
| ----------^^^^^^^^
| |
| returns a reference to data owned by the current function
| temporary value created here
Also, if have multiple parameters like this and you want to split them out into variables, be sure to take the Ref<'_, _> part on its own and use .as_deref() where you use it. Again this is needed to keep the intermediate Ref<'_, _> alive:
let a_ref = a.as_ref().map(RefCell::borrow);
let b_ref = b.as_ref().map(RefCell::borrow);
let c_ref = c.as_ref().map(RefCell::borrow);
f(a_ref.as_deref(), b_ref.as_deref(), c_ref.as_deref());

Related

Turning a RefMut into a &mut

use std::cell::RefCell;
use std::rc::Weak;
struct Elem {
attached_elem: Weak<RefCell<Elem>>,
value: i32,
}
impl Elem {
fn borrow_mut_attached_elem(&self) -> &mut Elem {
//what should this line be?
self.attached_elem.upgrade().unwrap().borrow_mut()
}
}
fn main(){}
I have tried some similar other lines but nothing has worked so far, even the experimental cell_leak feature for RefMut.
I don't mind changing the signature of the function I just want to reduce the overhead of getting a mutable reference to the attached_elem from an Elem.
what should this line be?
There is nothing you can put in that line to (safely) satisfy the function signature - and that's for good reason. While RefCell does allow obtaining &mut T from a RefCell<T> (that's why it exists), it must guarantee that only one mutable reference exist at a time. It does so by only providing a temporary reference whose lifetime is tied to the RefMut<T> wrapper. Once the wrapper is dropped, the value is marked as no longer borrowed, so the reference must not outlive it.
If Rust were to allow you to return a naked &mut Elem, you'd be able to use the reference after the RefCell has ceased being marked as borrowed. In that case, what's to stop you from calling borrow_mut_attached_elem() again, and obtain a second mutable reference to the same Elem?
So you'll definitely need to change the signature. If you just need to give outside code temporary access to &mut Elem, the easiest way is to accept a closure that will receive it. For example:
fn with_attached_elem<R>(&self, f: impl FnOnce(&mut Elem) -> R) -> R {
let rc = self.attached_elem.upgrade().unwrap();
let retval = f(&mut *rc.borrow_mut());
retval
}
You'd call it to do something with the element, e.g.:
elem.with_attached_elem(|e| e.value += 1);
with_attached_elem takes care to return the value returned by the closure, allowing you to collect data from &mut Elem and propagate it to the caller. For example, to pick up the value of the attached element you could use:
let value = elem.with_attached_elem(|e| e.value);

Lifetime problem with HashMap with HashMap references as values

(New to rust, FYI!)
So - I've been trying to grok the concept of lifetimes in Rust. I've read the documentation, and read some blogs and SO posts on the topic. But still not quite getting it (hence, possibly a bad title to the question).
I've got a particular problem I'm trying to figure out, which I've boiled down to this small sample code (tried to make as close to a working sample as I could):
use std::collections::HashMap;
// Imagine this is actually a more complex type:
type BigComplexType = i32;
fn some_expensive_computation() -> BigComplexType {
// Imagine this takes some arguments, and takes a long time to run
return 123;
}
fn construct() -> HashMap<i32, &'static HashMap<i32, BigComplexType>> {
let mut main = HashMap::new();
let mut nested = HashMap::new();
nested.insert(1111, some_expensive_computation());
nested.insert(2222, some_expensive_computation());
// ... and lots more inserts...
main.insert(10, &nested);
main.insert(20, &nested);
// ... and lots more inserts...
// Imagine a lot more other nested HashMaps to follow here
// let mut nested2 = ...
// ...
// main.insert(..., &nested2);
// ...
return main;
}
fn main() {
construct();
}
This example is a bit trivial - in the actual code, I'm creating much more complicated and deeper structures in the construct() function.
What I'm trying to do is to create some sort of cache that holds these pre-computed values, so that they can be easily and quickly accessed else in the code. Perhaps this can all just be done in some totally different way - but I figured there must be a way to do it this way.
But, rustc complains here, because nested only exists in construct(), and once we're out of the function, it ceases to exist, and thus all references are invalid (or, that's how I understand the problem).
I've tried to introduce a 'a lifetime to the construct() function, and use that lifetime on the nested HashMap below but no dice. Got some errors, and could never fully make it work. I've tried all sorts of variants of adding lifetime annotations, but no dice.
I have a feeling I'm just not grokking some aspect of the whole concept here. Can anyone help point me in the right direction?
(I did think about gathering the nested HashMap into a vector and returning along side the main hashmap - so that then the function is returning the main HashMap, plus a vector of the nested ones - thus, the lifetime would be guaranteed, I think - but I hit some other roadblocks trying that, and have a feeling I'm over-complicating things.)
For reference, this is the error I get in compiling the above:
error[E0515]: cannot return value referencing local variable `nested`
--> lifetime_return.rs:29:12
|
19 | main.insert(10, &nested);
| ------- `nested` is borrowed here
...
29 | return main;
| ^^^^ returns a value referencing data owned by the current function
error[E0515]: cannot return value referencing local variable `nested`
--> lifetime_return.rs:29:12
|
20 | main.insert(20, &nested);
| ------- `nested` is borrowed here
...
29 | return main;
| ^^^^ returns a value referencing data owned by the current function
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0515`.
Any/all help would be greatly appreciated! I tried to look for similar question, but couldn't spot one - perhaps I just didn't recognize some SO post as a duplicate, as I don't fully understand the lifetime model just yet.
Your intuition for why this doesn't work is correct: nested lives only inside construct, and you try to return references to it in the hashmap that live for longer than the function. Assuming that you don't want to clone the nested maps, presumably because they're very large, you can use Rc instead as a way to have trivially cloneable references to the nested maps that keep them alive for as long as necessary:
use std::collections::HashMap;
use std::rc::Rc;
type BigComplexType = i32;
fn some_expensive_computation() -> BigComplexType {
return 123;
}
fn construct() -> HashMap<i32, Rc<HashMap<i32, BigComplexType>>> {
let mut main = HashMap::new();
let mut nested = HashMap::new();
nested.insert(1111, some_expensive_computation());
nested.insert(2222, some_expensive_computation());
let nested_rc = Rc::new(nested);
main.insert(10, Rc::clone(&nested_rc));
main.insert(20, nested_rc); // can move the Rc for the last insert
let mut nested2 = HashMap::new();
nested2.insert(3333, some_expensive_computation());
nested2.insert(4444, some_expensive_computation());
let nested2_rc = Rc::new(nested2);
main.insert(30, Rc::clone(&nested2_rc));
main.insert(40, nested2_rc);
return main;
}
fn main() {
let map = construct();
println!("{}", map[&10][&1111]); // 123
}
Playground link

Avoiding "cannot move out of borrowed content" without the use of "to_vec"?

I'm learning rust and have a simple program, shown below. Playground link.
#[derive(Debug)]
pub struct Foo {
bar: String,
}
pub fn gather_foos<'a>(data: &'a Vec<Vec<&'a Foo>>) -> Vec<Vec<&'a Foo>> {
let mut ret: Vec<Vec<&Foo>> = Vec::new();
for i in 0..data.len() {
if meets_requirements(&data[i]) {
ret.push(data[i].to_vec());
}
}
return ret
}
fn meets_requirements<'a>(_data: &'a Vec<&'a Foo>) -> bool {
true
}
fn main() {
let foo = Foo{
bar: String::from("bar"),
};
let v1 = vec![&foo, &foo, &foo];
let v2 = vec![&foo, &foo];
let data = vec![v1, v2];
println!("{:?}", gather_foos(&data));
}
The program simply loops through an array of arrays of a struct, checks if the array of structs meets some requirement and returns an array of arrays that meets said requirement.
I'm sure there's a more efficient way of doing this without the need to call to_vec(), which I had to implement in order to avoid the error cannot move out of borrowed content, but I'm not sure what that solution is.
I'm learning about Box<T> now and think it might provide a solution to my needs? Thanks for any help!!
The error is showing up because you're trying to move ownership of one of the vectors in the input vector to the output vector, which is not allowed since you've borrowed the input vector immutably. to_vec() creates a copy, which is why it works when you use it.
The solution depends on what you're trying to do. If you don't need the original input (you only want the matched ones), you can simply pass the input by value rather than by reference, which will allow you to consume the vector and move items to the output. Here's an example of this.
If you do need the original input, but you don't want to copy the vectors with to_vec(), you may want to use references in the output, as demonstrated by this example. Note that the function now returns a vector of references to vectors, rather than a vector of owned vectors.
For other cases, there are other options. If you need the data to be owned by multiple items for some reason, you could try Rc<T> or Arc<T> for reference-counted smart pointers, which can be cloned to provide immutable access to the same data by multiple owners.

Why do I get "no method named push found for type Option" with a vector of vectors?

I tried to use a String vector inside another vector:
let example: Vec<Vec<String>> = Vec::new();
for _number in 1..10 {
let mut temp: Vec<String> = Vec::new();
example.push(temp);
}
I should have 10 empty String vectors inside my vector, but:
example.get(0).push(String::from("test"));
fails with
error[E0599]: no method named `push` found for type `std::option::Option<&std::vec::Vec<std::string::String>>` in the current scope
--> src/main.rs:9:20
|
9 | example.get(0).push(String::from("test"));
| ^^^^
Why does it fail? Is it even possible to have an vector "inception"?
I highly recommend reading the documentation of types and methods before you use them. At the very least, look at the function's signature. For slice::get:
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
where
I: SliceIndex<[T]>,
While there's some generics happening here, the important part is that the return type is an Option. An Option<Vec> is not a Vec.
Refer back to The Rust Programming Language's chapter on enums for more information about enums, including Option and Result. If you wish to continue using the semantics of get, you will need to:
Switch to get_mut as you want to mutate the inner vector.
Make example mutable.
Handle the case where the indexed value is missing. Here I use an if let.
let mut example: Vec<_> = std::iter::repeat_with(Vec::new).take(10).collect();
if let Some(v) = example.get_mut(0) {
v.push(String::from("test"));
}
If you want to kill the program if the value is not present at the index, the shortest thing is to use the index syntax []:
example[0].push(String::from("test"));

How can I get a reference to the key and value immediately after inserting into a `HashMap`?

use std::collections::HashMap;
use std::collections::hash_map::Entry::*;
fn hook(k: &str, v: &str) {}
fn tt(k: String, v: String) -> Option<String> {
let mut a: HashMap<String, String> = HashMap::new();
match a.entry(k) {
Occupied(mut occupied) => {
let old = occupied.insert(v);
//hook(&k, &old);
Some(old)
}
Vacant(vacant) => {
let v = vacant.insert(v);
let k = vacant.key(); // Why doesn't it work?
//hook(&k, v);
None
}
}
}
I would like to call hook immediately after a key is inserted into the HashMap. It seems I have to use Entry. However, I am unable to call vacant.key() right after vacant.insert.
TL;DR: You cannot (right now?)
The compiler tells you why it doesn't work. Don't hesitate to read Rust compiler messages; they aren't scary and a lot of effort has gone into them!
error[E0382]: use of moved value: `vacant`
--> src/main.rs:16:21
|
15 | let v = vacant.insert(v);
| ------ value moved here
16 | let k = vacant.key();
| ^^^^^^ value used here after move
|
= note: move occurs because `vacant` has type `std::collections::hash_map::VacantEntry<'_, std::string::String, std::string::String>`, which does not implement the `Copy` trait
This is just normal Rust code. VacantEntry::insert consumes selfby value, returning a reference to the inserted value:
fn insert(self, value: V) -> &'a mut V
There are over 100 other question/answer pairs that ask about this error message, so I'll assume you've read enough of them to understand the problem; that's why we answer questions on Stack Overflow after all!
So why is it this way? That's tougher to answer. When you insert into a HashMap, it takes ownership of the key and the value. When you use the entry API, there's an intermediate step - you've given ownership of the key to the entry. The entry also has a mutable reference to the HashMap; a "bookmark" of where the value is.
When the value is missing and then you insert a value, the key is transferred out of the entry and into the HashMap. This means that the reference to the key inside the entry would be invalidated. That's why you can't reorder the two lines.
However, thinking about it a bit deeper, the value returned from insert refers to the underlying HashMap, after the value has been inserted. I can't see any reason preventing a function from being added that returns a reference to the key and the value. However, such a function doesn't exist now.
See also How can I keep a reference to a key after it has been inserted into a HashMap?
I'm pretty sure in this case you don't need the functionality though.
If you want to call it for the new value, just do all of this before the insertion:
hook(&k, &v);
a.insert(k, v);
If you want to do it only for the old value, doing nothing when there wasn't previously a value, you can:
Occupied(mut occupied) => {
let old = occupied.insert(v);
hook(occupied.key(), &old);
Some(old)
}
If you want to call the hook with the old value if there was one and the new value if inserting (which seems inconsistent), you can call it before adding, the hook function will be none the wiser as the arguments are just references:
Vacant(vacant) => {
hook(vacant.key(), &v);
let v = vacant.insert(v);
None
}

Resources