-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mapping and collecting an owned Vec
to an item with the same size should reuse its allocation
#75087
Comments
I don't expect compiler optimizations can reasonably and reliably address either the transmute case nor the case where some non-trivial transformation happens but memory layout stays the same, but perhaps library code can help. The transmute scenario is probably most reliably addressed by rust-lang/rfcs#2756 plus (plus the results of the Safe Transmute project group (https://github.com/rust-lang/project-safe-transmute) if you want something safe). The other case, user-defined transform-in-place, might be addressed to some extent but special-casing in |
You might want to use something like https://docs.rs/map_in_place. |
@nbdd0121 Nice find. That's actually pretty much the exact API that I'd love to have, and I think the performance difference (allocations specifically) show that it would be a sensible addition. Of course, rustc could also accomplish this with specialization, but Edit: Here's a quick proof of concept that I believe does what I want. trait MapVecInPlace<T> {
fn map_in_place<U>(self, f: impl FnMut(T) -> U) -> Vec<U>;
}
impl<T> MapVecInPlace<T> for Vec<T> {
fn map_in_place<U>(mut self, mut f: impl FnMut(T) -> U) -> Vec<U> {
assert_eq!(size_of::<T>(), size_of::<U>());
assert_eq!(align_of::<T>(), align_of::<U>());
let ptr_range_start = self.as_mut_ptr();
// Safety: Both pointers are of the same type T, and no wrapping is involved.
let ptr_range_end = unsafe { ptr_range_start.add(self.len()) };
let mut ptr = ptr_range_start;
while ptr != ptr_range_end {
// Safety: The value is within the range of pointers where memory is initialized.
let t_value = unsafe { ptr.read() };
let u_value = f(t_value);
// Safety: We have a `U` returned from `f`. As we're writing this value, we need to
// cast this pointer. This is safe because size and alignment have already been
// checked.
unsafe { ptr.cast::<U>().write(u_value) };
// Safety: No wrapping is involved, as we're operating within the original vector.
ptr = unsafe { ptr.add(1) };
}
// Safety: We've transformed all T's to U's. Size and alignment is guaranteed to be the
// same due to asserts.
unsafe { transmute(self) }
}
} |
What you have is not safe. #[repr(u8)]
#[derive(Debug)]
enum Test {
A = 1,
B = 2,
}
impl Drop for Test {
fn drop(&mut self) {}
}
let v = vec![Test::A, Test::B];
let v2 = v.map_in_place(|x| match x {
Test::A => 0u8,
Test::B => panic!(),
}); Miri gives:
|
Right. You'd want to call |
I have a PR for this, but it regresses compile times and I haven't had the time yet to dig into why. #70793 |
@the8472 Does the now-merged PR resolve this issue? If so, I'll close it. |
That it does, https://rust.godbolt.org/z/3WK5M4 |
Thanks for the confirmation! Looks like the move still isn't optimized out (as it is when |
As it stands currently (on both stable and nightly), a new
Vec
is created in memory. As the source and target types are the same size in memory, I see no reason the compiler couldn't just map the value in-place, returning the same allocated ref (but with different semantics).The commented out line is to indicate what I think should be the case for a trivial case where memory representations are identical. Obviously it wouldn't be quite as simple for most cases.
The text was updated successfully, but these errors were encountered: