Reputation: 11
In the following code snippet I am trying to create a tuple that contains a String value and a struct that contains an attribute that is set as the reference to the first value (which would be the String) in the tuple.
My question is how do I make the String (which is the first value in the tuple) live long enough.
Here is the code:
#[derive(Debug, Clone)]
struct RefTestStruct {
key: usize,
ref_value: Option<&'static str>,
}
fn init3<'a>( cache: &Arc<Mutex<HashMap<usize, (String, Option<RefTestStruct>)>>> ) {
let mut handles: Vec<JoinHandle<()>> = vec![];
for idx in 0..10 {
//create reference copy of cache
let cache_clone = Arc::clone(cache);
let handle = thread::spawn(move || {
//lock cache
let mut locked_cache = cache_clone.lock().unwrap();
// add new object to cache
let tuple_value = (format!("value: {}", idx), None );
let mut ts_obj =
RefTestStruct {
key: idx,
ref_value: Some( tuple_value.0.as_str() ),
};
tuple_value.1 = Some(ts_obj);
locked_cache.insert(idx as usize, tuple_value);
println!("IDX: {} - CACHE: {:?}", idx, locked_cache.get( &(idx as usize) ).unwrap() )
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("\n");
}
fn main() {
// init
let cache = HashMap::<usize, (String, Option<RefTestStruct>)>::new();
let am_cache = Arc::new(Mutex::new(cache));
init3(&am_cache);
// change cache contents
let mut handles: Vec<JoinHandle<()>> = vec![];
for idx in 0..10 {
let cache_clone = Arc::clone(&am_cache);
let handle = thread::spawn(move || {
let mut locked_cache = cache_clone.lock().unwrap();
let tuple_value = locked_cache.get_mut( &(idx as usize) ).unwrap();
(*tuple_value).0 = format!("changed value: {}", (*tuple_value).1.unwrap().key + 11);
let ts_obj =
RefTestStruct {
key: (*tuple_value).1.unwrap().key,
ref_value: Some( (*tuple_value).0.as_str() ),
};
tuple_value.1 = Some(ts_obj);
// locked_cache.insert(idx as usize, tuple_value);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// display cache contents
let mut handles: Vec<JoinHandle<()>> = vec![];
for idx in 0..10 {
let cache_clone = Arc::clone(&am_cache);
let handle = thread::spawn(move || {
let locked_cache = cache_clone.lock().unwrap();
let ts_obj = locked_cache.get( &(idx as usize) ).unwrap();
println!("IDX: {} - CACHE: {:?}", idx, &ts_obj );
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
ERROR:
error[E0597]: `tuple_value.0` does not live long enough
--> src\main.rs:215:42
|
215 | ref_value: Some( tuple_value.0.as_str() ),
| ^^^^^^^^^^^^^^^^^^^^^^
| |
| borrowed value does not live long enough
| argument requires that `tuple_value.0` is borrowed for `'static`
...
221 | });
| - `tuple_value.0` dropped here while still borrowed
Upvotes: 0
Views: 1313
Reputation: 4867
String
reference between threads?First I answer the question in the caption and ignore the program you posted (And I'll try to ignore my itch to guess what you really want to do.)
I'll assume, you want to avoid unsafe in your own code. (So dependencies with unsafe code might be ok.)
To pass a reference to a thread, the reference has to live as long as the thread will live.
Your options:
'static
lifetime on your reference.'static
lifetime.'static
lifetime on the reference.So now the questions changed into, how to obtain a reference to
a String
with 'static
lifetime.
We can't use a constant, as we can't initialize it.
This leaves us with the possibility to Box
the String
and leak
it.
But of course, this leaks memory, as the String
can't be dropped any more. Also you get a &'mut String
out
of it, so only one thread can have it at a time and after being
done with it, pass it on. So to me it looks like it is essentially
the same you could do with the owned value of type String
too,
so why bother and risk the memory leak. (Putting a Arc<Mutex<..>>
around it, is also "same you coud do with the owned value".).
Other methods might exist, which I am not aware of.
These are called scoped threads and guarantee, that the thread exits, before the lifetime ends. These are not in standard rust yet, but there are crates implementing those (I guess using unsafe).
For example crossbeam
offers an implementation of this.
Looking at your program though, I guess you want to have long lived threads.
The program you posted has a different issue besides.
You try to create a self referential value in your cache. As far as I know, this is not possible in rust, without using a shared ownership tool, like Arc
.
If you try to use a plain Arc
, the access would be read only and users of the cache could only change, which String
the cache entry points
to, but not the "pointed to" String
itself.
So If one user replaced the Arc<String>
in the cache, other threads would still have a reference to the "old" String
and don't see the changed value. But I guess that is what you want to achieve.
But that is a conceptual problem, you can't have unsynchronized read access to a value, which can then be mutated concurrently (by whatever means).
So, if you want to have the ability to change the value with all prior users being able to see it, this leaves you with Mutex
or RWLock
depending on the access pattern, you are anticipating.
A solution using Arc<String>
with the aforementioned defects:
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
#[derive(Debug, Clone)]
struct RefTestStruct {
key: usize,
_ref_value: Arc<String>,
}
type Cache = HashMap<usize, (Arc<String>, RefTestStruct)>;
type AmCache = Arc<Mutex<Cache>>;
fn init3(cache: &AmCache) {
let mut handles: Vec<JoinHandle<()>> = vec![];
for idx in 0..10_usize {
//create reference copy of cache
let cache_clone = Arc::clone(cache);
let handle = thread::spawn(move || {
//lock cache
let mut locked_cache = cache_clone.lock().unwrap();
// add new object to cache
let s = Arc::new(format!("value: {}", idx));
let ref_struct = RefTestStruct {
key: idx,
_ref_value: s.clone(),
};
let tuple_value = (s, ref_struct);
locked_cache.insert(idx, tuple_value);
println!(
"IDX: {} - CACHE: {:?}",
idx,
locked_cache.get(&idx).unwrap()
)
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("\n");
}
fn main() {
// init
let cache = Cache::new();
let am_cache = Arc::new(Mutex::new(cache));
init3(&am_cache);
// change cache contents
let mut handles: Vec<JoinHandle<()>> = vec![];
for idx in 0..10_usize {
let cache_clone = Arc::clone(&am_cache);
let handle = thread::spawn(move || {
let mut locked_cache = cache_clone.lock().unwrap();
let tuple_value = locked_cache.get_mut(&idx).unwrap();
let new_key = tuple_value.1.key + 11;
let new_s = Arc::new(format!("changed value: {}", new_key));
(*tuple_value).1 = RefTestStruct {
key: new_key,
_ref_value: new_s.clone(),
};
(*tuple_value).0 = new_s;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// display cache contents
let mut handles: Vec<JoinHandle<()>> = vec![];
for idx in 0..10_usize {
let cache_clone = Arc::clone(&am_cache);
let handle = thread::spawn(move || {
let locked_cache = cache_clone.lock().unwrap();
let ts_obj = locked_cache.get(&idx).unwrap();
println!("IDX: {} - CACHE: {:?}", idx, &ts_obj);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
I am sure it is not difficult to derive the Arc<Mutex<String>>
version from that, if desired.
That would not be as pointless, as one might think. The Mutex
around the cache protects the HashMap
for insertion and deletion. And the Mutex
around the String
values, protects the string values themselves and can be locked indepently of the Mutex
around the HashMap
. Usual caveats apply, if you ever need both locks at the same time, lock them in the same order always, or risk a deadlock.
Upvotes: 2