@@ -139,180 +139,3 @@ impl<T> Vec<T> {
139
139
140
140
141
141
142
-
143
- # Handling Zero-Sized Types
144
-
145
- It's time. We're going to fight the spectre that is zero-sized types. Safe Rust
146
- * never* needs to care about this, but Vec is very intensive on raw pointers and
147
- raw allocations, which are exactly the * only* two things that care about
148
- zero-sized types. We need to be careful of two things:
149
-
150
- * The raw allocator API has undefined behaviour if you pass in 0 for an
151
- allocation size.
152
- * raw pointer offsets are no-ops for zero-sized types, which will break our
153
- C-style pointer iterator.
154
-
155
- Thankfully we abstracted out pointer-iterators and allocating handling into
156
- RawValIter and RawVec respectively. How mysteriously convenient.
157
-
158
-
159
-
160
-
161
- ## Allocating Zero-Sized Types
162
-
163
- So if the allocator API doesn't support zero-sized allocations, what on earth
164
- do we store as our allocation? Why, ` heap::EMPTY ` of course! Almost every operation
165
- with a ZST is a no-op since ZSTs have exactly one value, and therefore no state needs
166
- to be considered to store or load them. This actually extends to ` ptr::read ` and
167
- ` ptr::write ` : they won't actually look at the pointer at all. As such we * never* need
168
- to change the pointer.
169
-
170
- Note however that our previous reliance on running out of memory before overflow is
171
- no longer valid with zero-sized types. We must explicitly guard against capacity
172
- overflow for zero-sized types.
173
-
174
- Due to our current architecture, all this means is writing 3 guards, one in each
175
- method of RawVec.
176
-
177
- ``` rust,ignore
178
- impl<T> RawVec<T> {
179
- fn new() -> Self {
180
- unsafe {
181
- // !0 is usize::MAX. This branch should be stripped at compile time.
182
- let cap = if mem::size_of::<T>() == 0 { !0 } else { 0 };
183
-
184
- // heap::EMPTY doubles as "unallocated" and "zero-sized allocation"
185
- RawVec { ptr: Unique::new(heap::EMPTY as *mut T), cap: cap }
186
- }
187
- }
188
-
189
- fn grow(&mut self) {
190
- unsafe {
191
- let elem_size = mem::size_of::<T>();
192
-
193
- // since we set the capacity to usize::MAX when elem_size is
194
- // 0, getting to here necessarily means the Vec is overfull.
195
- assert!(elem_size != 0, "capacity overflow");
196
-
197
- let align = mem::align_of::<T>();
198
-
199
- let (new_cap, ptr) = if self.cap == 0 {
200
- let ptr = heap::allocate(elem_size, align);
201
- (1, ptr)
202
- } else {
203
- let new_cap = 2 * self.cap;
204
- let ptr = heap::reallocate(*self.ptr as *mut _,
205
- self.cap * elem_size,
206
- new_cap * elem_size,
207
- align);
208
- (new_cap, ptr)
209
- };
210
-
211
- // If allocate or reallocate fail, we'll get `null` back
212
- if ptr.is_null() { oom() }
213
-
214
- self.ptr = Unique::new(ptr as *mut _);
215
- self.cap = new_cap;
216
- }
217
- }
218
- }
219
-
220
- impl<T> Drop for RawVec<T> {
221
- fn drop(&mut self) {
222
- let elem_size = mem::size_of::<T>();
223
-
224
- // don't free zero-sized allocations, as they were never allocated.
225
- if self.cap != 0 && elem_size != 0 {
226
- let align = mem::align_of::<T>();
227
-
228
- let num_bytes = elem_size * self.cap;
229
- unsafe {
230
- heap::deallocate(*self.ptr as *mut _, num_bytes, align);
231
- }
232
- }
233
- }
234
- }
235
- ```
236
-
237
- That's it. We support pushing and popping zero-sized types now. Our iterators
238
- (that aren't provided by slice Deref) are still busted, though.
239
-
240
-
241
-
242
-
243
- ## Iterating Zero-Sized Types
244
-
245
- Zero-sized offsets are no-ops. This means that our current design will always
246
- initialize ` start ` and ` end ` as the same value, and our iterators will yield
247
- nothing. The current solution to this is to cast the pointers to integers,
248
- increment, and then cast them back:
249
-
250
- ``` rust,ignore
251
- impl<T> RawValIter<T> {
252
- unsafe fn new(slice: &[T]) -> Self {
253
- RawValIter {
254
- start: slice.as_ptr(),
255
- end: if mem::size_of::<T>() == 0 {
256
- ((slice.as_ptr() as usize) + slice.len()) as *const _
257
- } else if slice.len() == 0 {
258
- slice.as_ptr()
259
- } else {
260
- slice.as_ptr().offset(slice.len() as isize)
261
- }
262
- }
263
- }
264
- }
265
- ```
266
-
267
- Now we have a different bug. Instead of our iterators not running at all, our
268
- iterators now run * forever* . We need to do the same trick in our iterator impls.
269
- Also, our size_hint computation code will divide by 0 for ZSTs. Since we'll
270
- basically be treating the two pointers as if they point to bytes, we'll just
271
- map size 0 to divide by 1.
272
-
273
- ``` rust,ignore
274
- impl<T> Iterator for RawValIter<T> {
275
- type Item = T;
276
- fn next(&mut self) -> Option<T> {
277
- if self.start == self.end {
278
- None
279
- } else {
280
- unsafe {
281
- let result = ptr::read(self.start);
282
- self.start = if mem::size_of::<T>() == 0 {
283
- (self.start as usize + 1) as *const _
284
- } else {
285
- self.start.offset(1);
286
- }
287
- Some(result)
288
- }
289
- }
290
- }
291
-
292
- fn size_hint(&self) -> (usize, Option<usize>) {
293
- let elem_size = mem::size_of::<T>();
294
- let len = (self.end as usize - self.start as usize)
295
- / if elem_size == 0 { 1 } else { elem_size };
296
- (len, Some(len))
297
- }
298
- }
299
-
300
- impl<T> DoubleEndedIterator for RawValIter<T> {
301
- fn next_back(&mut self) -> Option<T> {
302
- if self.start == self.end {
303
- None
304
- } else {
305
- unsafe {
306
- self.end = if mem::size_of::<T>() == 0 {
307
- (self.end as usize - 1) as *const _
308
- } else {
309
- self.end.offset(-1);
310
- }
311
- Some(ptr::read(self.end))
312
- }
313
- }
314
- }
315
- }
316
- ```
317
-
318
- And that's it. Iteration works!
0 commit comments