The problem is: I use that vector as a buffer, clean it up after use. But rustc doesn't know the vector is empty when moving it somewhere, it requires me to give a lifetime annotation, but it is actually unnecessary. Is that a crate has achieved this, or how to achieve it(I know this must need unsafe code)? Thanks.
You only need the Vec inside print_short_name, so declare it inside that function and remove the field.
fn print_short_name(&self, times: usize) {
let mut cache_vec: Vec<&str> = vec![];
// we needs this vector as a buffer to temporarily
// store `&str`s, and removes them at the end.
self.node.find_short_in(&mut cache_vec);
for _ in 0..times {
for string in cache_vec.iter() {
println!("{}", string);
}
}
}
But if I call this function very frequently, it may cause many allocate operations. I want to do some optimization here. sorry I forgot to say this before.
You are not going to be able to put short-lived references in a long-lived type. As far as the type system is concerned, that would create a dangling reference.
Yes, but the workaround is the same to @jendrikw 's: if the main(assume it is not a main but a common function) be called frequently, the frequent allocation appears again.
In real case, the struct like ShortNamePrinter is deeply nested, giving a mutable reference of a buffer vector will greatly increase the complexity of the api. To do so, I have to add a &mut Vec<...> in each function's arguments. I hope there is a way to keep its allocation in memory.
As I commented in the code, calling function find_short_in is time-consuming. To simplify the example, I wrote finding strings that is short. find_short_in is called as many times as the iterators are traversed. I need a vector to decrease the overhead.
use std::alloc::Layout;
fn reuse_vec<T, U>(mut vec: Vec<T>) -> Vec<U> {
vec.clear();
// Can the following assertion be checked at compile-time?
assert_eq!(Layout::new::<T>(), Layout::new::<U>());
// SAFETY: `vec` is an empty `Vec` and the
// elements' memory layout doesn't change.
unsafe {
std::mem::transmute::<Vec<T>, Vec<U>>(vec)
}
}
fn main() {
let mut reused: Vec<&str> = { // note we need a type annotation here!
/* … */
};
/* … */
}
This second example will fail to detect bad conversions at compile-time and throw a runtime error, which isn't nice. You'll see that if you remove the type annotation in the first line of main.
Please don't transmute stuff behind indirection – this includes pointers and pointer-containing collections. If you need to "cheat" with the lifetimes, do it at the point where it is required. (Your transmute-wrapping fn lacking unsafe also makes it unsound, btw.)
The above example could be better implemented by storing a Vec<&'static str> and then using it for storing temporary elements, then clearing it. Needless to say, it's also dangerous and bad practice.
You don't need any unsafe code to reuse the buffer. There's a feature in the standard library that can be exploited for this purpose: Vec iteration is specialized so that if you consume a Vec and produce a Vec, the allocation is reused if possible, even if the type is different.
Thus, you can change the lifetime to 'a by a dummy map(), as long as the len() of the vector is 0 when you do it:
In this playground program I've modified the original code to use this technique. Debug prints show that the allocation is reused after it is created the first time:
Thanks for explaining. But if the generic type TVec<T> has is opaque, we don't know if T is a reference or not, how to deal with it?
I modified Vec<&'static str> to Vec<u8>, the debug info said the pointer changed to 0x1, which declares the allocation is not reused.
What does "behind indirection" mean? Because the elements of the Vec are behind a pointer (that points to the heap)?
I can't see why my first example should be unsound. Afterall, it doesn't change types but only lifetimes and these are existing only during compile-time (to reject programs).
What do you mean? Do the transmutation of each element in the Vec? That sounds more dangerous to me.
What exactly is unsound and why?
If that example you gave works, then it's certainly favorable to anything that uses unsafe, but I feel like it's not very obvious that .into_iter().map(…).collect() preserves the allocation. (I believe it does, but it's still a bit mysterious to me.)
I feel like a lifetime transmutation (apart from being unsafe) is more clear in regard to what happens (but happy if I'm proven wrong or if someone can explain to me what's unsound about my code example).
Relying on the optimizer, in contrast, seems to be more unreliable to me.
I didn't say it was unsound. But transmuting pointers is dangerous, because if transmute<T, U> is valid, then it does not imply that transmute<&T, &U> or transmute<*T, *U> is valid, or vice versa.
Yes.
reuse_vec is unsound, because it is not unsafe, but the annotated lifetimes are unconstrained, so you can use this function to create dangling pointers in safe code.