Global mutable HashMap in a library [duplicate] - hashmap

This question already has answers here:
How do I create a global, mutable singleton?
(7 answers)
Closed 7 years ago.
I want to have an extendable dictionary linking together Object with a &'static str inside my library. HashMap seems like the right data structure for this, but how do I make it global, initialised on declaration and mutable?
So something like this:
use std::collections::HashMap;
enum Object { A, B, C }
const OBJECT_STR: &'static [&'static str] = &[ "a", "b", "c" ];
static mut word_map: HashMap<&'static str, Object> = {
let mut m = HashMap::new();
m.insert(OBJECT_STR[0], Object::A);
m.insert(OBJECT_STR[1], Object::B);
m.insert(OBJECT_STR[2], Object::C);
m
};
impl Object {
...
}

This would be possible with the lazy_static crate. As seen in their example. Since mutablity accessing a static variable is unsafe, it would need to wrapped into a Mutex. I would recommend not making the HashMap public, but instead provide a set of methods that lock, and provide access to the HashMap. See this answer on making a globally mutable singleton.
#[macro_use]
extern crate lazy_static;
use std::collections::HashMap;
use std::sync::Mutex;
lazy_static! {
static ref HASHMAP: Mutex<HashMap<u32, &'static str>> = {
let mut m = HashMap::new();
m.insert(0, "foo");
m.insert(1, "bar");
m.insert(2, "baz");
Mutex::new(m)
};
}
fn main() {
let mut map = HASHMAP.lock().unwrap();
map.insert(3, "sample");
}

Related

How do I get a simple mutable thread-local struct in Rust?

I'm building an interpreter with a garbage collector. I want a thread-local nursery region, and a shared older region. I am having trouble setting up the nursery. I have:
const NurserySize : usize = 25000;
#[thread_local]
static mut NurseryMemory : [usize;NurserySize] = [0;NurserySize];
thread_local! {
static Nursery: AllocableRegion = AllocableRegion::makeLocal(unsafe{&mut NurseryMemory});
}
#[cfg(test)]
mod testMemory {
use super::*;
#[test]
fn test1() {
Nursery.with(|n| n.allocObject(10));
}
}
First question is why do I need the unsafe - NurseryMemory is thread local, so access can't be unsafe?
Second question is how can I actually use this? The code is at playground, but it doesn't compile and attempts I've made to fix it seem to make it worse.
1. Why is unsafe required to get a reference to a mutable ThreadLocal?
The same reason that you need unsafe for a normal mutable static,
you would be able to create aliasing mut pointers in safe code.
The following incorrectly creates two mutable references to the mutable thread local.
#![feature(thread_local)]
#[thread_local]
static mut SomeValue: Result<&str, usize> = Ok("hello world");
pub fn main() {
let first = unsafe {&mut SomeValue};
let second = unsafe {&mut SomeValue};
if let Ok(string) = first {
*second = Err(0); // should invalidate the string reference, but it doesn't
println!("{}", string) // as first and second are considered to be disjunct
}
}
first wouldn't even need to be a mutable reference for this to be a problem.
2. How to fix the code?
You could use a RefCell around the AllocatableRegion to dynamically enforce the borrowing of the inner value.
const NurserySize : usize = 25000;
#[thread_local]
static mut NurseryMemory : [usize;NurserySize] = [0;NurserySize];
thread_local! {
static Nursery: RefCell<AllocableRegion> = RefCell::new(AllocableRegion::makeLocal(unsafe{&mut NurseryMemory}));
}
#[cfg(test)]
mod testMemory {
use super::*;
#[test]
fn test1() {
Nursery.with(|n| n.borrow_mut().allocObject(10));
}
}
You don't need unsafe to make a mutable thread-local struct in rust. However, Nursery does need to be a RefCell. This is sufficient:
use std::cell::RefCell;
const NURSERY_SIZE: usize = 300;
thread_local! {
static NURSERY: RefCell<[usize; NURSERY_SIZE]> = RefCell::new([0; NURSERY_SIZE]);
}
#[cfg(test)]
mod test_memory {
use super::*;
#[test]
fn test1() {
NURSERY.with(|n| n.borrow_mut()[10] = 20);
}
}
Rust Playground link

"expected bound lifetime parameter, found concrete lifetime" on StreamExt .scan() method

I am using async-tungstenite to listen to a websocket, and async-std's StreamExt to operate on the resulting stream.
I want to use a HashMap to accumulate the latest Ticker values from the websocket. These Ticker values will be looked up later to be used in calculations. I'm using the symbol (String) value of the Ticker struct as the key for the HashMap. I'm using the .scan StreamExt method to perform the accumulation.
However, I get a compilation error related to lifetimes. Here's some stripped-down code:
let tickers = HashMap::new();
let mut stream = ws.
.scan(tickers, accumulate_tickers);
while let msg = stream.next().await {
println!("{:?}", msg)
}
...and the accumulate_tickers function:
fn accumulate_tickers(tps: &mut HashMap<String, Ticker>, bt: Ticker) -> Option<&HashMap<String, Ticker>> {
tps.insert((*bt.symbol).to_string(), bt);
Some(tps)
}
The compilation error I receive is as follows:
error[E0271]: type mismatch resolving `for<'r> <for<'s> fn(&'s mut std::collections::HashMap<std::string::String, ws_async::model::websocket::Ticker>, ws_async::model::websocket::Ticker) -> std::option::Option<&'s std::collections::HashMap<std::string::String, ws_async::model::websocket::Ticker>> {accumulate_tickers} as std::ops::FnOnce<(&'r mut std::collections::HashMap<std::string::String, ws_async::model::websocket::Ticker>, ws_async::model::websocket::Ticker)>>::Output == std::option::Option<_>`
--> examples/async_std-ws.rs:64:4
|
64 | .scan(tickers, accumulate_tickers);
| ^^^^ expected bound lifetime parameter, found concrete lifetime
I'm unaware of a way to provide a lifetime parameter to the scan method.
I wonder whether the issue may be related to the fact that I modify the HashMap and then try to return it (is it a move issue?). How could I resolve this, or at least narrow down the cause?
I was able to get this working. Working my way through the compiler errors, I ended up with this signature for the accumulate_tickers function:
fn accumulate_tickers<'a>(tps: &'a mut &'static HashMap<String, Ticker>, bt: Ticker) -> Option<&'static HashMap<String, Ticker>>
I do want the accumulator HashMap to have a static lifetime so that makes sense. tps: &'a mut &'static HashMap... does look a bit strange, but it works.
Then, this issue was was that tickers also had to have a static lifetime (it's the initial value for the accumulator. I tried declaring it as static outside the main but it wouldn't let me set it to the result of a function - HashMap::new().
I then turned to lazy_static which allows me to create a static value which does just that:
lazy_static! {
static ref tickers: HashMap<String, Ticker> = HashMap::new();
}
This gave me a HashMap accumulator that had a static lifetime. However, like normal static values declared in the root scope, it was immutable. To fix that, I read some hints from the lazy_static team and then found https://pastebin.com/YES8dsHH. This showed me how to make my static accumulator mutable by wrapping it in Arc<Mutex<_>>.
lazy_static! {
// from https://pastebin.com/YES8dsHH
static ref tickers: Arc<Mutex<HashMap<String, Ticker>>> = {
let mut ts = HashMap::new();
Arc::new(Mutex::new(ts))
};
}
This does mean that I have to retrieve the accumulator from the Mutex (and lock it) before reading or modifying it but, again, it works.
Putting it all together, the stripped-down code now looks like this:
#[macro_use]
extern crate lazy_static;
lazy_static! {
// from https://pastebin.com/YES8dsHH
static ref tickers: Arc<Mutex<HashMap<String, Ticker>>> = {
let mut ts = HashMap::new();
Arc::new(Mutex::new(ts))
};
}
// SNIP
// Inside main()
let mut ticks = ws
.scan(&tickers, accumulate_tickers);
while let Some(msg) = ticks.next().await {
println!("{:?}", msg.lock().unwrap());
}
// SNIP
fn accumulate_tickers<'a>(tps: &'a mut &'static tickers, bt: Ticker) -> Option<&'static tickers> {
tps.lock().unwrap().insert((*bt.symbol).to_string(), bt);
Some(tps)
}
I'd be happy to hear suggestions for ways in which this could be made simpler or more elegant.

Create immutable HashMap from Cargo build script

I'm using something similar to this answer to load some file data into a Rust binary. I'd like this data to be stored in a HashMap, so I'm able to search by key in the main program. However, I'm not sure how to do it in a way that ensures this data is immutable.
From the vector previously defined in build.rs, I assume I could do something like this in main.rs:
const ALL_THE_FILES: &[(&str, &[u8])] = &include!(concat!(env!("OUT_DIR"), "/all_the_files.rs"));
fn main () {
let all_the_files_hashmap: HashMap<&str, &[u8]> = ALL_THE_FILES.iter().cloned().collect();
// ...
}
Is there any way to construct this HashMap directly from build.rs and load it to a const in main.rs? If I replace the tuple-vector approach used in the linked answer with a HashMap definition iterating with something like data.insert(<key>,<value>);, can I load this into main.rs with include! in the same way, keeping the HashMap immutable?
Thanks a lot!
Regards
The easiest way to create a global read-only hashmap is to use lazy_static:
#[macro_use]
extern crate lazy_static;
use std::collections::HashMap;
const ALL_THE_FILES: &[(&str, &[u8])] = ...;
lazy_static! {
static ref FILE_TO_DATA: HashMap<&'static str, &'static [u8]> = {
let mut m = HashMap::new();
for (file, data) in ALL_THE_FILES {
m.insert(*file, *data);
}
m
};
}
fn main() {
println!("{:?}", FILE_TO_DATA.get("foo").unwrap());
}
Playground.
You could move this logic into code generated by build.rs, but it would be much less obvious what's going on.

How do I initialize an opaque C struct when using Rust FFI?

Here's what I would like to do in C code:
#include <some_lib.h>
int main() {
some_lib_struct_t x;
some_lib_func(&x);
}
How do I make use of the library in Rust? Here's what I've got so far:
extern crate libc; // 0.2.51
struct some_lib_struct_t;
#[link(name = "some_lib")]
extern "C" {
fn some_lib_func(x: *mut some_lib_struct_t);
}
fn main() {
let mut x: some_lib_struct_t;
unsafe {
some_lib_func(&mut x);
}
}
When compiling I get an error:
error[E0381]: borrow of possibly uninitialized variable: `x`
--> src/main.rs:13:23
|
13 | some_lib_func(&mut x);
| ^^^^^^ use of possibly uninitialized `x`
The safest answer is to initialize the struct yourself:
let mut x: some_lib_struct_t = some_lib_struct_t;
unsafe {
some_lib_func(&mut x);
}
The closest analog to the C code is to use MaybeUninit
use std::mem::MaybeUninit;
unsafe {
let mut x = MaybeUninit::uninit();
some_lib_func(x.as_mut_ptr());
}
Before Rust 1.36, you can use mem::uninitialized:
unsafe {
let mut x: some_lib_struct_t = std::mem::uninitialized();
some_lib_func(&mut x);
}
You have to be sure that some_lib_func completely initializes all the members of the struct, otherwise the unsafety will leak outside of the unsafe block.
Speaking of "members of the struct", I can almost guarantee your code won't do what you want. You've defined some_lib_struct_t as having zero size. That means that no stack space will be allocated for it, and a reference to it won't be what your C code is expecting.
You need to mirror the definition of the C struct in Rust so that the appropriate size, padding, and alignment can be allocated. Usually, this means using repr(C).
Many times, C libraries avoid exposing their internal struct representation by always returning a pointer to the opaque type:
What's the Rust idiom to define a field pointing to a C opaque pointer?
In Rust how can I define or import a C struct from a third party library?
Allocating an object for C / FFI library calls
From the documentation for mem::uninitialized():
Deprecated since 1.39.0: use mem::MaybeUninit instead
The new solution would look like this:
use std::mem::MaybeUninit;
let instance = unsafe {
let mut x: MaybeUninit<some_lib_struct_t> = MaybeUninit::uninit();
some_lib_func(x.as_mut_ptr());
x.assume_init()
}
After reading Shepmaster's answer, I looked closer at the header for the library. Just as they said, some_lib_struct_t was just a typedef for a pointer to actual_lib_struct_t. I made the following changes:
extern crate libc;
struct actual_lib_struct_t;
type some_lib_type_t = *mut actual_lib_struct_t;
#[link(name="some_lib")]
extern {
fn some_lib_func(x: *mut some_lib_type_t);
}
fn main() {
let mut x: some_lib_type_t;
unsafe {
x = std::mem::uninitialized();
some_lib_func(&mut x);
}
}
And it works! I do however get the warning found zero-size struct in foreign module, consider adding a member to this struct, #[warn(improper_ctypes)] on by default.

How to pass a member function of a struct to another struct as callback

I want to pass a member function of a struct to another struct.
Sorry, poor English, can't say more details.
use std::thread;
struct Struct1 {}
impl Struct1 {
pub fn do_some(&mut self, s: &str) {
// do something use s to modify self
}
}
struct Struct2 {
cb1: Box<Fn(&mut Struct1, &str)>,
}
fn main() {
let s1 = Struct1 {};
let s2 = Struct2 {
cb1: Box::new(s1.do_some), // how to store do_some function in cb1 ?
};
}
You were very close! To refer to a method or any other symbol you use the :: separator and specify the path to said symbol. Methods or associated functions live in the namespace of the type, therefore the path of your method is Struct1::do_some. In Java you would also use the . operator to access those, but in Rust the . operator is only used on existing objects, not on type names.
The solution thus is:
let s2 = Struct2 {
cb1: Box::new(Struct1::do_some),
};
However, you could possibly improve the type of your function a bit. Box<Fn(...)> is a boxed trait object, but you don't necessarily need that if you don't want to work with closures. If you just want to refer to "normal functions" (those who don't have an environment), you can use a function pointer instead:
struct Struct2 {
cb1: fn(&mut Struct1, &str),
}
Note the lowercase fn and that we don't need the Box.

Resources