Free webinar! How to Make Generative AI Work for You
Get my free ticket+ 12
Sorry for being a little offtopic, I am not a Rust programmer, but I tremendously enjoy reading this thread. I wish most questions on SL were of similar quality... It would definitely motivate me to spend more time in the forum 😅
A fresh one on Rust's growing popularity:
https://www.nature.com/articles/d41586-020-03382-2
+ 11
XXX I'm not sure if you stumbled onto this post on your own or if you're answering the call from my earlier post. Regardless, I'm glad you did as this has quickly become one of my favorite Q&A discussions in a long while.
It's gotten me digging into the Rust documentation to fill in the gaps of this thread. It's been a long while since I've had to research something new like this.
Coder Kitten I want to give you kudos for asking such a great question that has revealed some intricate aspects of this language that has further piqued my interest beyond my surface level awareness.
It's the first time since delving into a language like Elixir that I've felt this intrigued about a language. This post might just be the point that nudged me over the edge to carve out time and learn this language.
Thanks guys for driving such a great thread. 🙏
+ 8
Oh... and by the way, Amazon just started a campaign to aggressively hirer Rust developers and are doubling down in their commitment to this language. I also know that Microsoft has taken a strong interest in Rust. This could be a solid language for people looking to accelerate their careers.
https://www.zdnet.com/article/amazon-were-hiring-software-engineers-who-know-programming-language-rust/
+ 8
Tibor Santa I completely agree with you.
If you look at the Discuss tab in this app, this question is ranked 6th in Hot Today list.
It's truly sad to see the other five Q&A Posts currently ranked higher than this one.
Let's cherish this brief moment of a quality thread and hope it somehow leads to more like this one.
*Edit:*
This moved to 4th position. But the point is still the same. It's a lone sample of what could be.
+ 7
Coder Kitten [Part 1]
Quoted from the book (https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value)
"The or_insert method on Entry is defined to return a mutable reference to the value for the corresponding Entry key if that key exists, and if not, inserts the parameter as the new value for this key and returns a mutable reference to the new value. This technique is much cleaner than writing the logic ourselves and, in addition, plays more nicely with the borrow checker."
std::collections::HashMap::entry - https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry
std::collections::hash_map::Entry - https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html
std::collections::hash_map::Entry::or_insert - https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.or_insert
+ 7
Coder Kitten [Part 2]
For this to work however, you will need to change lines 6 and 16 from
T: Fn(U) -> R
to
T: Fn(&U) -> R
This is because we don't want the function to take the ownership of the key. I also believe that this will prevent problems in the long run, without actually affecting the code much. Taking references is much better than taking by value.
Here is a working example
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5c3dd639b62bdc48b00ce2d21b3e4aaa
+ 7
Coder Kitten [Part 2]
The best solution IMO, would be using std::rc::Rc. You probably know about it if you have read the official book. The only downside is that it have a little bit of runtime cost because it allocates on the heap, but that would be very minute, as compared to using `Copy` or `Clone` (imagine cloning a 100 element vector of 100 element vectors).
std::rc::Rc - https://doc.rust-lang.org/std/rc/struct.Rc.html
Rc in the rust official book - https://doc.rust-lang.org/book/ch15-04-rc.html
Working example
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=95b8c72a62a20249400d234aa1df0672
Although I still believe using Rc here would be unnecessary. Maybe Aaron Eberhardt can help. He knows Rust.
+ 7
Coder Kitten I ran your code on sololearn.
https://code.sololearn.com/caEKq4O1489z/?ref=app
+ 6
Coder Kitten
[Part 1]
Oh yes, my solution forgets the whole point of cache.
But... are you sure the way you rewrote it works? It should give an error as on the line
`self.results.insert(arg, r)`
you are moving `arg` into the the scope of the `insert` method, which is why it is unusable on the line
`self.results.get(&arg).unwrap()`
I even checked
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6ae88c74b0e61b4449c2398222072c1a
It might be working for you because you probably forgot to remove the constraint of the `Copy` trait on type parameters `R` and `U`, i.e. you might have forgotten to remove `+ Copy` on line 7 and 17, and `R: Copy` on line 8 and 18 (in the code mentioned in the question body).
+ 6
Here's my version that only requires Clone and no Copy:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5a93e0f0d204e3df5f7ea42277d8b931
+ 6
[Off-topic]
David Carroll Tibor Santa I think that this one of the features of Rust I really like. It compels you to think about a problem from a perspective you probably wouldn't in other languages, which results in you coming out with an even better, optimized solution. In this case, garbage collected languages would simply add to the reference count of the variable and simple increase the validity scope of the variable, and others will maybe just pass out a copy of the value. But Rust is right now making us think how do we use values in different scopes without copying them or making their references dangling, and at the same time not suffer the runtime cost of a garbage collector.
+ 5
Anyone familiar enough with Rust to answer this question?
Coder Kitten The linked code snippet is so pleasant to look at. This is the appeal that has so many experienced developers so intrigued about the Rust language.
I would try to answer, but it wouldn't be based on knowledge gained from first hand experience with Rust.
For this reason, I've posted this request to my network hoping it casts a wider reach on your behalf. 😉
+ 5
Coder Kitten that's a very interesting question and to be honest I've never encountered such a hard ownership problem. First, the thing with the Copy trait is, that it implies that copying the data type is very efficient. That's great for small structs and primitive types but not for everything. The Clone trait however is pretty common and does not imply efficiency on cloning a data type. So you probably want to reduce the requirements from Copy to Clone to make it more general purpose.
But of course that does not solve any ownership problems, it just makes it visible where the cloning (or copying) of an data type happens.
The ownership problem boils down to this: The HashMap owns the keys and the values yet you need to return the value without a reference (because you can't return references to local variables). This makes it impossible to borrow which means you need to clone the data...
Smart pointers won't help either but maybe RefCell or Cow could work, by enforcing borrow rules during runtime.
+ 5
Coder Kitten I must admit I was partially wrong xD
I just couldn't accept that Rust was unable to actually do this without copying the data every time which would be super inefficient so I though about it a little more. So here's what I came up with:
The HashMap owns all the data, which makes sense. But this also means that you can't really borrow the data in this context because you could delete or replace the data later which would invalidate borrowed references which would be unsafe. This means, you can't eliminate the need to call clone somewhere because Rust won't allow you to move any data out of the HashMap. But you can at least use Rc, like XXX suggested to only clone a pointer, not the data. Rc guarantees Rust that there will always be a valid reference which solves the problem and even eliminates the Clone dependency for the R type. I think this could be the solution for your problem:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4f5da3122172a59f74a8b3edd7b0f668
+ 5
(a bit off-topic)
the reason don't have dive into Rust is that isn't here in SL yet.
so why not write all an email with same title and send it to SL asking for include Rust. Just think about; lessons, assignments, challenges..
If someone with many followers, David Carroll for example, posts it, maybe enough people joins and SL team hears us.
+ 5
Coder Kitten the problem you just mentioned can be solved by changing the function to
pub fn value(&mut self, arg: U) -> &R {
/* here you are not actually asking for a reference to value inside the HashMap,
which would have happened if you called .get()
Instead, you are calling .get() inside the `if` block, which is completely unrelated to the `else` block
*/
if self.results.contains_key(&arg) {
self.results.get(&arg).unwrap()
} else {
let r = (self.f)(&arg);
self.results.insert(arg, r);
self.results.get(&arg).unwrap() // this is still an issue
}
}
It solves the one-mutable-and-one-immutable-reference problem, but the other problem still persists.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7bc4d4a1797e4d85cacb27de47811479
+ 4
Btw. there's a crate for caching function values called cached that also required the Clone trait to be implemented so they haven't found a solution either. Maybe you ask your question on reddit where more smart people tend to hang around.
+ 3
Coder Kitten isn't that very similar to my first solution? Calling clone is usually fine but sometimes expansive, for example on a large Vec. So I think my second solution should perform much better on large data.