-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Vec::recycle #2802
RFC: Vec::recycle #2802
Conversation
seems similar to #2756 You can use my use vec_utils::VecExt;
let mut objects: Vec<Object> = Vec::new(); // Any lifetime will do here
while let Some(byte_chunk) = stream.next() { // `byte_chunk` lifetime starts
// Consumes `objects`, creating a new `Vec` `temp`.
let mut temp: Vec<Object> = objects.drop_and_reuse(); // `temp` lifetime starts
// let mut temp = objects.drop_and_reuse::<Object>(); // or if you prefer
// Zero-copy parsing; `Object`s has references to `byte_chunk`
deserialize(byte_chunk, &mut temp)?;
process(&temp)?;
// "Returns" `object` back to where it was.
objects = temp.drop_and_reuse(); // `temp` lifetime ends
} // `byte_chunk` lifetime ends |
The difference with transmuting vectors is that this pattern doesn't transmute or reinterpret any arbitrary values, and the RCFs argues it's a safe pattern.
I didn't know about vec-utils, thanks for linking it. I'll add it to the prior work tomorrow. I also prepared the API as a crate as a companion to the RFC: (Ninja edits for precise quotes) |
One difference between our apis is that I just cteate a new |
That seems to be a good pattern. There is possibly room for two APIs for both behaviours. On the other hand, if we some day get compile time asserts, I'd like for it to fail in cases where I thought it wouldn't allocate but the layout doesn't match and it would be forced to. |
Is this the same as |
@SimonSapin yes, this is the same as that |
I updated the implementation to reflect the changes I did here: rust-lang/rust#66069 This includes using Some things I mentioned as unresolved questions are becoming clearer:
|
…out the possibility of allocating a new Vec on size/alignment mismatch.
This is Vec::clear then Vec::transmute with no bound because you can't witness the previous type in a bad state? that seems neat, but probably also not RFC required, since it's just one method. |
@Lokathor: The rule whether an RFC is required isn't all clear to me, so I decided to write one if not just to spell out all the considerations. There is also an open PR. |
An RFC doesn't hurt! Usually they're reserved for major changes to the standard library, changes to how cargo/rustc build, and changes to the language itself (eg: adding an attribute). I'm always happy to have folks be aware of allocations though! |
@@ -205,6 +205,13 @@ directly subtyping relations between two generic types in where clauses. [1] | |||
Thus, it was deemed to be sufficient to limit verification of the memory layout to the checking | |||
of size and alignment of the stored type. | |||
|
|||
There is an alternative to this API, provided by https://crates.io/crates/vec_utils, | |||
that instead of panicking on mismatching size or alignment, just allocates a new `Vec` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vec-utils
does not allocate a new Vec
, it just returns Vec::new()
if the layouts are incompatible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, indeed, Vec::new()
doesn't allocate.
Syntactically and semantically this is already possible. Alas, rust-lang/rust#62645 means it doesn't actually work. |
Just wanted to drop a note that I think |
Just to drop a note; I am going to update this RFC to incorporate the current state of discussion the next weekend. |
An update about checking the invariants: it seems that recent advances in const features have unlocked the type size/alignment checking at compile time: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=cd871dc7385e7a1d8a40216cb27f778b Now, I wish we had a rough understanding what's the timeline for the features const_fn, const_if_match and const_panic to stabilise. |
Being able to constrain a where clause by a const bool is still an open issue. |
@the8472 where clauses are not necessary!
As a disclaimer: I do not recommend that we do this! This will lead to C++ level of bad error reporting. We need where clauses to get good error reporting, or some compiler intrinsics/extensions which will allow us to create good error reports. For example, const #![feature(const_generics)]
#![feature(const_fn)]
#![feature(const_if_match)]
#![feature(const_panic)]
#![allow(unused)]
pub mod layout_checker {
use std::marker::PhantomData;
pub struct LayoutToken<T, U, const WORK: ()>(PhantomData<(T, U)>);
pub const fn check<T, U>() -> LayoutToken<
T, U,
{
if core::mem::size_of::<T>() != core::mem::size_of::<U>()
|| core::mem::align_of::<T>() != core::mem::align_of::<U>() {
panic!("The types have different layouts");
}
}
> {
LayoutToken(PhantomData)
}
}
unsafe fn transmute<T, U>(value: T) -> U {
// this will panic at compile time post-monomorphization if layouts are incompatible
layout_checker::check::<T, U>();
std::mem::transmute_copy(&std::mem::ManuallyDrop::new(value) as &T)
}
fn main() {
let bar = unsafe { transmute::<_, u32>(10_i32) };
// let bar = unsafe { transmute::<_, u8>(10_i32) }; // fails to compile!
} |
yeah, I supposed that's sufficient. I was thinking of having it spelled it in the recycle signature itself impl Vec<T> {
fn recycle<U>(self) -> Vec<U>
where Is<{size_of::<T>() == size_of::<U>() && align_of::<T>() == align_of::<U>()}>: True
{ ... }
} |
@the8472 Yes, that would be much nicer |
That’s not quite the same. fn _static_assert_size() {
let _ = std::mem::transmute::<Foo, [u8; 24]>;
} |
from what ive seen it doesn't work with generics though. |
@SimonSapin, I completely forgot about that behaviour! @Lokathor that's because it asserts it's layout concerns before monomorphozation, but to work with generics, it should assert that afterwards. |
I think I think |
Yeah I like this because then you could do |
@the8472 I agree that panics aren't good. Here's my current thinking about the situation:
I'm sorry I haven't had the time to reflect the discussion to the RCF text. However, I refrain doing so right now as there are new discussion points. |
At least the performance aspect of compile-time vs. runtime branching is orthogonal to using Warnings also are an orthogonal concern, if the caller wants warnings they could use something like Recycle is an optimization, imo neither panics nor compile errors are the right price to pay for optimizations and difficult to silence warnings from stdlib aren't great either. Leaving most of this to the caller and mentioning those approaches in the documation seems the right approach to me. After all it's mostly a method to avoid people reaching for |
Would it make sense to have both a
If I'm using recycling and updating a dependency causes recycling to no longer be used, I'd much rather find out about it right when I compile instead of when I hit the particular code path.
@the8472 Does |
That may be fine if you are the author of a bin crate. But you couldn't uphold your semver responsibilities this way if you're a lib crate author unless you pinned the involved dependencies to a single version.
Not in stable. In nightly you could build something like that with proc macro diagnostics.
Once you have try_recycle you can build the other one from it. And imo it's not such a common use case where a convenience wrapper would be worth it in the stdlib. |
@the8472 The If I'm not very familiar with proc macro diagnostics, so my analysis may be incorrect. |
Ah yeah, you're right. I thought that could could also be used in a const eval context so one could have a static warn with a condition (not in the unwrap lambda) similar to a static assert, but that doesn't actually work. But I think the standard library has no internal capabilities for emitting compile time warnings either. So the only other options than returning a result are either panics or compile time errors. And both can be done on the caller side. |
This is a relevant development: "Allow if and match in constants" is going to be stabilised in 1.45: rust-lang/rust#49146 |
You could impose a weaker bound than equal sizes really. Alignments could shrink too, right? |
@burdges I played with that thought too but I wonder what the allocator API guarantees have to say about returning a piece of memory with different layout than what it was originally allocated with. |
For slices, yes. For |
Improve code formatting
Note that with recent iterator optimizations you can get recycle behavior automatically and not only in the transmute (preserve all elements) or recycle (discard all elements) cases but any other cases that process some of the elements. |
This could use the traits added by safe-transmute to bound what can be recycled, allowing changing the run-time panic to a compile-time error. |
We discussed this in today's @rust-lang/libs-api meeting. Our rough consensus:
|
Thanks for the update. I'm delighted to find I agree with the Libs API team consensus. I've been sitting on this RFC, waiting for further const features to stabilize that would enable more compile-time checking, so it's been in a kind of a limbo. Do you think I should close this now and re-open when it's implementable? In that case, what's the current preferred process, sending an RFC PR or just an implementation PR against rust-lang/rust? (For what it's worth, I originally opened this as an RFC specifically considering that the implementation required unsafe.) |
Sure. Or maybe just open a new RFC PR instead at that point, linking to this one.
That's fine too. Just be aware that we might still ask for an RFC if the additions turns out to be controversial from an API perspective. |
Note that fn recycle(mut v: Vec<A>) -> Vec<B> {
v.clear();
v.into_iter().map(|_| unreachable!()).collect()
} This will be a 'no-op' if the layout matches. Otherwise, it'll just deallocate the vector and return a new empty one instead. |
Thanks for the comments, closing this for now! I'm gonna follow up when the time is ripe. |
Would love to see this tried again now that rust is more mature |
I just released a new version of the recycle_vec crate. https://github.com/golddranks/recycle_vec The new version manages to check the size and aligment requirements with a static check, using a trick proposed here: golddranks/recycle_vec#2 As @m-ou-se remarked, the pattern seems to be already to be possible with I'll send another PR and see how it's going to be received. |
Add method
Vec::recycle
that allows safe reuse of the backing allocation of theVec
, helping avoiding allocations and de-allocations in tight loops.Rendered