|
| 1 | +- Feature Name: collection-transmute |
| 2 | +- Start Date: 2019-09-05 |
| 3 | +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) |
| 4 | +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Add a `transmute(self)` method to `Vec`, `VecDeque` and `BinaryHeap`. |
| 10 | + |
| 11 | +# Motivation |
| 12 | +[motivation]: #motivation |
| 13 | + |
| 14 | +The mentioned types cannot safely be transmuted by `mem::transmute`. Adding a |
| 15 | +method for that purpose will hopefully discourage users to try anyway. |
| 16 | + |
| 17 | +E.g. the following code is UB: |
| 18 | + |
| 19 | +```rust |
| 20 | +let x = vec![0u32; 2]; |
| 21 | +let y = unsafe { std::mem::transmute::<_, Vec<[u8; 4]>>(x) }; |
| 22 | +``` |
| 23 | + |
| 24 | +This is explained in the docs for [`Vec`], but with an unsound solution. The |
| 25 | +way to do this correctly turns out surprisingly tricky: |
| 26 | + |
| 27 | +```rust |
| 28 | +let x = vec![0u32; 2]; |
| 29 | +let y = unsafe { |
| 30 | + let y: &mut Vec<_> = &mut *ManuallyDrop::new(x); |
| 31 | + Vec::from_raw_parts(y.as_mut_ptr() as *mut [u8; 4], |
| 32 | + y.len(), |
| 33 | + y.capacity()) |
| 34 | +}; |
| 35 | +``` |
| 36 | + |
| 37 | +Though the code is not too large, there are a good number of things to get |
| 38 | +wrong – this solution was iteratively created by soundness-knowledgeable |
| 39 | +Rustaceans, with multiple wrong attempts. So this method seems like a good |
| 40 | +candidate for inclusion into `std`. |
| 41 | + |
| 42 | +This also applies to `VecDeque` and `BinaryHeap`, which are implemented in |
| 43 | +terms of `Vec`. |
| 44 | + |
| 45 | +[`Vec`]: TODO |
| 46 | + |
| 47 | +# Guide-level explanation |
| 48 | +[guide-level-explanation]: #guide-level-explanation |
| 49 | + |
| 50 | +The types `std::vec::Vec`, `std::collections::VecDeque` and |
| 51 | +`std::collections::BinaryHeap` all get a new unsafe `transmute` method that |
| 52 | +takes `self` by value and returns a new `Vec`/`VecDeque`/`BinaryHeap` with a |
| 53 | +caller-chosen item type. |
| 54 | + |
| 55 | +The API might look like this (exemplified for `Vec`): |
| 56 | + |
| 57 | +```rust |
| 58 | +impl<T> Vec<T> { |
| 59 | + /// Transmute this `Vec` to a different item type |
| 60 | + /// |
| 61 | + /// # Safety |
| 62 | + /// |
| 63 | + /// Calling this function requires the target item type be compatible with |
| 64 | + /// `Self::Item` (see [`mem::transmute`]). |
| 65 | + /// |
| 66 | + /// # Examples |
| 67 | + /// |
| 68 | + /// transmute a `Vec` of 32-bit integers to their byte representations: |
| 69 | + /// |
| 70 | + /// ``` |
| 71 | + /// let x = vec![0u32; 5]; |
| 72 | + /// let y = unsafe { x.transmute::<[u8; 4]>() }; |
| 73 | + /// assert_eq!(5, y.len()); |
| 74 | + /// assert_eq!([0, 0, 0, 0], y[0]); |
| 75 | + /// ``` |
| 76 | + /// |
| 77 | + /// [`mem::transmute`]: ../../std/mem/fn.transmute.html |
| 78 | + unsafe fn transmute<I>(self) -> Vec<I> { |
| 79 | + .. |
| 80 | + } |
| 81 | +``` |
| 82 | + |
| 83 | +This would mean our example above would become: |
| 84 | + |
| 85 | +```rust |
| 86 | +let x = vec![0i32; 2]; |
| 87 | +let y = x.transmute::<[u8; 4]>(); |
| 88 | +``` |
| 89 | + |
| 90 | +The documentation of `mem::transmute` should link to the new methods. |
| 91 | + |
| 92 | +A clippy lint can catch offending calls to `mem::transmute` and suggest using |
| 93 | +the inherent `transmute` method where applicable. |
| 94 | + |
| 95 | +# Reference-level explanation |
| 96 | +[reference-level-explanation]: #reference-level-explanation |
| 97 | + |
| 98 | +The implementation for `Vec` copies the above solution. The methods for |
| 99 | +`VecDeque` and `BinaryHeap` use the `Vec` method on their data. |
| 100 | + |
| 101 | +# Drawbacks |
| 102 | +[drawbacks]: #drawbacks |
| 103 | + |
| 104 | +Adding a new method to `std` increases code size and needs to be maintained. |
| 105 | +However, this seems to be a minor inconvenience when compared to the safety |
| 106 | +benefit. |
| 107 | + |
| 108 | +# Rationale and alternatives |
| 109 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 110 | + |
| 111 | +As explained above, the method is useful, yet non-obvious. There were multiple |
| 112 | +wrong implementation attempts before that were not chosen for being unsound. |
| 113 | + |
| 114 | +We could do nothing, but this means users wanting to transmute the items of a |
| 115 | +`Vec` will be left without an obvious solution which will lead to unsound code |
| 116 | +if they get it wrong. |
| 117 | + |
| 118 | +We could document the correct solution instead of putting it into `std`. This |
| 119 | +would lead to worse ergonomics. |
| 120 | + |
| 121 | +We could create a trait to hold the `transmute` method. This would allow more |
| 122 | +generic usage, but might lead to worse ergonomics due to type inference |
| 123 | +uncertainty. |
| 124 | + |
| 125 | +It would even be possible to provide a default implementation using |
| 126 | +`mem::transmute`, but having a default implementation that might be unsound for |
| 127 | +some types is a footgun waiting to happen. |
| 128 | + |
| 129 | +# Prior art |
| 130 | +[prior-art]: #prior-art |
| 131 | + |
| 132 | +`mem::transmute` offers the same functionality for many other types. We have |
| 133 | +added similar methods to different types where useful, see the various |
| 134 | +iterator-like methods in `std`. @Shnatsel and @danielhenrymantilla came up |
| 135 | +with the solution together in a |
| 136 | +[clippy issue](https://github.com/rust-lang/rust-clippy/issues/4484). |
| 137 | + |
| 138 | +# Unresolved questions |
| 139 | +[unresolved-questions]: #unresolved-questions |
| 140 | + |
| 141 | +- are there other types that would benefit from such a method? What about |
| 142 | +`HashSet`, `HashMap` and `LinkedList`? |
| 143 | +- would it make sense to add implementations to types where `mem::transmute` is |
| 144 | +acceptable, to steer people away from the latter? |
| 145 | +- this RFC does not deal with collection types in crates such as |
| 146 | +[`SmallVec`](https://docs.rs/smallvec), though it is likely an implementation |
| 147 | +in `std` might motivate the maintainers to include similar methods. |
| 148 | + |
| 149 | +# Future possibilities |
| 150 | +[future-possibilities]: #future-possibilities |
| 151 | + |
| 152 | +The author cannot think of anything not already outlined above. |
0 commit comments