-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add support for in-place map for Vec
s of types with same size
#15302
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
Conversation
/// | ||
/// Fails if not all `T`s were popped, also fails if not the same amount of | ||
/// `U`s was pushed before calling `unwrap`. | ||
pub fn unwrap(self) -> Vec<U> { |
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.
This should probably be called into_vec
.
@huonw I addressed your comment. |
The use case for this is something along the lines of being able to typesafely convert a read
In Rust with that change it would be:
|
While this seems like a very real problem, adding a specific solution for only the Do you know if it would be possible to extend this to the |
There's always the option of just using |
@thestinger This only works if the source and the target type are the same, |
@alexcrichton I thought about extending it to other containers, and it looks like this isn't easily possible, as the container's internal memory layout could differ between a That being said, the general pattern could be applied to different containers as well, such as for the keys of a hashmap, the entries of a linked list, the keys in a tree, etc. |
Is it useful to expose the |
Yes, that would work too. |
|
||
impl<T,U> PartialVec<T,U> { | ||
/// Creates a `PartialVec` from a `Vec`. | ||
pub fn new(mut vec: Vec<T>) -> PartialVec<T,U> { |
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.
This should perhaps be called from_vec()
instead.
I'm not a fan of the usage of I'd also love to see the zero-sized type restriction lifted. An |
As for generalizing this to other containers, it could actually theoretically work for any container that has the following invariants:
Given this, the This does require less obviously-safe code, of course. And I am a bit uncomfortable about maintaining parallel iterators like that, because if the iterator uses a And of course there's no way for the compiler to enforce the above invariants. A trait would have to be written that documents the invariants (and provides the relevant methods), and all implementors would have to be sure they meet said invariants. For the trait itself, HKTs would be extremely helpful in representing the type signature, but they're not actually necessary. If the trait takes two type parameters, the "from" container type and the "to" container type, then it can work. In fact, this would actually let you map between two disparate container definitions if they use the same memory layout under the hood (and opted in to this via the trait conformance). Unfortunately, due to the need for defining a pub trait InPlaceIterable<'a, T, It: Iterator<&'a mut T>> {
fn in_place_mut_iter(&'a mut self) -> It;
}
/// Implementors of this trait must follow the documented invariants.
/// Any implementor of this trait will also need to implement
/// InPlaceIterable to actually be useful
pub trait InPlaceMappable {
fn ensure_drop_no_elements(&mut self); // set_len(0)
}
// C_T is the "source" collection, e.g. Vec<T>
// C_U is the "destination" collection e.g. Vec<U>
pub struct PartialMapper<C_T, C_U> { ... }
impl<'a, T, U, T_It: Iterator<&'a mut T>,
C_T: InPlaceIterable<'a, T, T_It> + InPlaceMappable,
// C_U doesn't need InPlaceIterable because we only construct iterators from
// C_T, and transmute the &mut T to &mut U as necessary.
// Similarly it doesn't need InPlaceMappable
C_U> PartialMapper<C_T, C_U> {
pub fn new(source: C_T) -> PartialMapper<C_T, C_U> { ... }
pub fn to_dest(self) -> C_U { ... }
pub fn shift(&mut self) -> Option<T> { ... }
pub fn push(&mut self, val: U) { ... }
} |
@kballard I have explained above why it can't work unless we know the internal memory layout of the container, see for example Otherwise, this still has some problems. What about containers that keep their elements sorted, or hashed, like HashSet, some kind of binary tree or else. In the end it looks like this can almost only meaningfully be supported for |
@tbu- Well, it can work for any container that can vend an That said, I think you're right in assuming that |
@kballard It doesn't really work for But you're right that it could be useful for |
@tbu- I was making assumptions about |
Incidentally, the |
@kballard I don't think so:
The
|
@kballard It's not just null-pointer optimization btw, we have also have unspecified struct layout, so other enum optimizations could come. |
@tbu- Err yeah, you're right, I misinterpreted. It's not used as the stride between two elements. But it is used. So I guess you need to assert that both |
@tbu- I'm actually unclear on how the unspecified struct layout is supposed to behave in the face of type parameters. On the one hand, transmuting between differently-parameterized variants of the same type assumes that the layout is stable for all type parameters, but on the other hand, allowing the layout to differ depending on the parameters is a potential optimization that we'd like to have. Assuming that the layout is not required to be stable for all possible parameters to a type, it's actually technically not even correct to transmute between Given that, I think that you need to verify what guarantees are made about the layout of generic types. If the layout of |
I think it would be very reasonable for |
@kballard That |
@tbu- True, you can build from raw parts. If you're doing that as a transmute-equivalent you will need to make sure that e.g. |
Of course, you could also have At this point I'm going to stop talking about how we could theoretically do this for arbitrary collections. I do like exploring the topic, but I'm uncomfortable with the number of invariants that the compiler is unable to check, and as you pointed out most collections actually can't take advantage of this. |
This is implemented using a new struct `PartialVec` which implements the proper drop semantics in case the conversion is interrupted by an unwind.
This specifically includes: - Fix of the tests - Remove `transmute` between `Vec`s of different types
Closing due to inactivity, but feel free to reopen with a rebase! |
minor: Sync from rust
This is implemented using a new struct
PartialVec
which implements the properdrop semantics in case the conversion is interrupted by an unwind.