From 6edd32bd97b9194c85196b1951bd6a9f15eb310e Mon Sep 17 00:00:00 2001 From: pwbh <127856937+pwbh@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:07:08 +0300 Subject: [PATCH 1/7] Added text related to handling dropping in raw vec for ZSTs --- src/vec/vec-zsts.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/vec/vec-zsts.md b/src/vec/vec-zsts.md index 6715f94..657ba9c 100644 --- a/src/vec/vec-zsts.md +++ b/src/vec/vec-zsts.md @@ -214,3 +214,22 @@ impl DoubleEndedIterator for RawValIter { ``` And that's it. Iteration works! + +And that's it. Iteration works! + +One last thing that we need to take into account, is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and infact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause an undefined behaviour if we try to deallocate something that we never allocated (obviously and for the right reasons). Lets fix tha, in our raw_vec we are going to tweak our Drop trait and check that we deallocate only types that are sized. + +```rust,ignore +impl Drop for RawVec { + fn drop(&mut self) { + println!("RawVec Drop called, deallocating memory"); + if self.cap != 0 && std::mem::size_of::() > 0 { + let layout = std::alloc::Layout::array::(self.cap).unwrap(); + unsafe { + std::alloc::dealloc(self.ptr.as_ptr() as *mut _, layout); + } + } + } +} +``` + From e6299305229941d52a9f3a736bf735f669f0b2af Mon Sep 17 00:00:00 2001 From: pwbh <127856937+pwbh@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:07:48 +0300 Subject: [PATCH 2/7] remove duplicate of a sentence --- src/vec/vec-zsts.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/vec/vec-zsts.md b/src/vec/vec-zsts.md index 657ba9c..20a4054 100644 --- a/src/vec/vec-zsts.md +++ b/src/vec/vec-zsts.md @@ -1,13 +1,13 @@ # Handling Zero-Sized Types It's time. We're going to fight the specter that is zero-sized types. Safe Rust -*never* needs to care about this, but Vec is very intensive on raw pointers and +_never_ needs to care about this, but Vec is very intensive on raw pointers and raw allocations, which are exactly the two things that care about zero-sized types. We need to be careful of two things: -* The raw allocator API has undefined behavior if you pass in 0 for an +- The raw allocator API has undefined behavior if you pass in 0 for an allocation size. -* raw pointer offsets are no-ops for zero-sized types, which will break our +- raw pointer offsets are no-ops for zero-sized types, which will break our C-style pointer iterator. Thankfully we abstracted out pointer-iterators and allocating handling into @@ -30,6 +30,7 @@ Due to our current architecture, all this means is writing 3 guards, one in each method of `RawVec`. + ```rust,ignore impl RawVec { fn new() -> Self { @@ -108,6 +109,7 @@ nothing. The current solution to this is to cast the pointers to integers, increment, and then cast them back: + ```rust,ignore impl RawValIter { unsafe fn new(slice: &[T]) -> Self { @@ -126,12 +128,13 @@ impl RawValIter { ``` Now we have a different bug. Instead of our iterators not running at all, our -iterators now run *forever*. We need to do the same trick in our iterator impls. +iterators now run _forever_. We need to do the same trick in our iterator impls. Also, our size_hint computation code will divide by 0 for ZSTs. Since we'll basically be treating the two pointers as if they point to bytes, we'll just map size 0 to divide by 1. Here's what `next` will be: + ```rust,ignore fn next(&mut self) -> Option { if self.start == self.end { @@ -152,13 +155,13 @@ fn next(&mut self) -> Option { Do you see the "bug"? No one else did! The original author only noticed the problem when linking to this page years later. This code is kind of dubious -because abusing the iterator pointers to be *counters* makes them unaligned! -Our *one job* when using ZSTs is to keep pointers aligned! *forehead slap* +because abusing the iterator pointers to be _counters_ makes them unaligned! +Our _one job_ when using ZSTs is to keep pointers aligned! _forehead slap_ Raw pointers don't need to be aligned at all times, so the basic trick of -using pointers as counters is *fine*, but they *should* definitely be aligned -when passed to `ptr::read`! This is *possibly* needless pedantry -because `ptr::read` is a noop for a ZST, but let's be a *little* more +using pointers as counters is _fine_, but they _should_ definitely be aligned +when passed to `ptr::read`! This is _possibly_ needless pedantry +because `ptr::read` is a noop for a ZST, but let's be a _little_ more responsible and read from `NonNull::dangling` on the ZST path. (Alternatively you could call `read_unaligned` on the ZST path. Either is fine, @@ -166,6 +169,7 @@ because either way we're making up a value from nothing and it all compiles to doing nothing.) + ```rust,ignore impl Iterator for RawValIter { type Item = T; @@ -215,8 +219,6 @@ impl DoubleEndedIterator for RawValIter { And that's it. Iteration works! -And that's it. Iteration works! - One last thing that we need to take into account, is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and infact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause an undefined behaviour if we try to deallocate something that we never allocated (obviously and for the right reasons). Lets fix tha, in our raw_vec we are going to tweak our Drop trait and check that we deallocate only types that are sized. ```rust,ignore @@ -232,4 +234,3 @@ impl Drop for RawVec { } } ``` - From 50d4aa63623a288a9faf9011d71af392680f2cbc Mon Sep 17 00:00:00 2001 From: pwbh <127856937+pwbh@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:08:12 +0300 Subject: [PATCH 3/7] Revert "remove duplicate of a sentence" This reverts commit c50c194f23715ed209e302ceb45900093f798184. --- src/vec/vec-zsts.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vec/vec-zsts.md b/src/vec/vec-zsts.md index 20a4054..657ba9c 100644 --- a/src/vec/vec-zsts.md +++ b/src/vec/vec-zsts.md @@ -1,13 +1,13 @@ # Handling Zero-Sized Types It's time. We're going to fight the specter that is zero-sized types. Safe Rust -_never_ needs to care about this, but Vec is very intensive on raw pointers and +*never* needs to care about this, but Vec is very intensive on raw pointers and raw allocations, which are exactly the two things that care about zero-sized types. We need to be careful of two things: -- The raw allocator API has undefined behavior if you pass in 0 for an +* The raw allocator API has undefined behavior if you pass in 0 for an allocation size. -- raw pointer offsets are no-ops for zero-sized types, which will break our +* raw pointer offsets are no-ops for zero-sized types, which will break our C-style pointer iterator. Thankfully we abstracted out pointer-iterators and allocating handling into @@ -30,7 +30,6 @@ Due to our current architecture, all this means is writing 3 guards, one in each method of `RawVec`. - ```rust,ignore impl RawVec { fn new() -> Self { @@ -109,7 +108,6 @@ nothing. The current solution to this is to cast the pointers to integers, increment, and then cast them back: - ```rust,ignore impl RawValIter { unsafe fn new(slice: &[T]) -> Self { @@ -128,13 +126,12 @@ impl RawValIter { ``` Now we have a different bug. Instead of our iterators not running at all, our -iterators now run _forever_. We need to do the same trick in our iterator impls. +iterators now run *forever*. We need to do the same trick in our iterator impls. Also, our size_hint computation code will divide by 0 for ZSTs. Since we'll basically be treating the two pointers as if they point to bytes, we'll just map size 0 to divide by 1. Here's what `next` will be: - ```rust,ignore fn next(&mut self) -> Option { if self.start == self.end { @@ -155,13 +152,13 @@ fn next(&mut self) -> Option { Do you see the "bug"? No one else did! The original author only noticed the problem when linking to this page years later. This code is kind of dubious -because abusing the iterator pointers to be _counters_ makes them unaligned! -Our _one job_ when using ZSTs is to keep pointers aligned! _forehead slap_ +because abusing the iterator pointers to be *counters* makes them unaligned! +Our *one job* when using ZSTs is to keep pointers aligned! *forehead slap* Raw pointers don't need to be aligned at all times, so the basic trick of -using pointers as counters is _fine_, but they _should_ definitely be aligned -when passed to `ptr::read`! This is _possibly_ needless pedantry -because `ptr::read` is a noop for a ZST, but let's be a _little_ more +using pointers as counters is *fine*, but they *should* definitely be aligned +when passed to `ptr::read`! This is *possibly* needless pedantry +because `ptr::read` is a noop for a ZST, but let's be a *little* more responsible and read from `NonNull::dangling` on the ZST path. (Alternatively you could call `read_unaligned` on the ZST path. Either is fine, @@ -169,7 +166,6 @@ because either way we're making up a value from nothing and it all compiles to doing nothing.) - ```rust,ignore impl Iterator for RawValIter { type Item = T; @@ -219,6 +215,8 @@ impl DoubleEndedIterator for RawValIter { And that's it. Iteration works! +And that's it. Iteration works! + One last thing that we need to take into account, is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and infact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause an undefined behaviour if we try to deallocate something that we never allocated (obviously and for the right reasons). Lets fix tha, in our raw_vec we are going to tweak our Drop trait and check that we deallocate only types that are sized. ```rust,ignore @@ -234,3 +232,4 @@ impl Drop for RawVec { } } ``` + From 761293eb4359233880446859a6a7a80405feda1d Mon Sep 17 00:00:00 2001 From: pwbh <127856937+pwbh@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:08:39 +0300 Subject: [PATCH 4/7] remove duplication --- src/vec/vec-zsts.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vec/vec-zsts.md b/src/vec/vec-zsts.md index 657ba9c..9778759 100644 --- a/src/vec/vec-zsts.md +++ b/src/vec/vec-zsts.md @@ -215,8 +215,6 @@ impl DoubleEndedIterator for RawValIter { And that's it. Iteration works! -And that's it. Iteration works! - One last thing that we need to take into account, is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and infact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause an undefined behaviour if we try to deallocate something that we never allocated (obviously and for the right reasons). Lets fix tha, in our raw_vec we are going to tweak our Drop trait and check that we deallocate only types that are sized. ```rust,ignore From a85100bb99d1d7b52e6d385a5c609ed17e2d241f Mon Sep 17 00:00:00 2001 From: pwbh <127856937+pwbh@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:09:34 +0300 Subject: [PATCH 5/7] remove comma --- src/vec/vec-zsts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vec/vec-zsts.md b/src/vec/vec-zsts.md index 9778759..d23ca40 100644 --- a/src/vec/vec-zsts.md +++ b/src/vec/vec-zsts.md @@ -215,7 +215,7 @@ impl DoubleEndedIterator for RawValIter { And that's it. Iteration works! -One last thing that we need to take into account, is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and infact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause an undefined behaviour if we try to deallocate something that we never allocated (obviously and for the right reasons). Lets fix tha, in our raw_vec we are going to tweak our Drop trait and check that we deallocate only types that are sized. +One last thing that we need to take into account is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and infact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause an undefined behaviour if we try to deallocate something that we never allocated (obviously and for the right reasons). Lets fix tha, in our raw_vec we are going to tweak our Drop trait and check that we deallocate only types that are sized. ```rust,ignore impl Drop for RawVec { From cbe78036f2f70157e20ef90077fe89b5d92091a3 Mon Sep 17 00:00:00 2001 From: pwbh <127856937+pwbh@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:26:08 +0200 Subject: [PATCH 6/7] Update src/vec/vec-zsts.md Co-authored-by: Jon Bauman --- src/vec/vec-zsts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vec/vec-zsts.md b/src/vec/vec-zsts.md index d23ca40..f6d7278 100644 --- a/src/vec/vec-zsts.md +++ b/src/vec/vec-zsts.md @@ -215,7 +215,7 @@ impl DoubleEndedIterator for RawValIter { And that's it. Iteration works! -One last thing that we need to take into account is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and infact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause an undefined behaviour if we try to deallocate something that we never allocated (obviously and for the right reasons). Lets fix tha, in our raw_vec we are going to tweak our Drop trait and check that we deallocate only types that are sized. +One last thing that we need to take into account is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and in fact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause undefined behavior if we try to deallocate something that we never allocated (obviously and for the right reasons). To fix that, in our `RawVec` we are going to tweak our `Drop` trait and check that we deallocate only types that are sized. ```rust,ignore impl Drop for RawVec { From 625d9de680b6e739cc20fc7ad810bfaf62419bc1 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 5 Jul 2025 16:11:37 +0900 Subject: [PATCH 7/7] Update src/vec/vec-zsts.md --- src/vec/vec-zsts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vec/vec-zsts.md b/src/vec/vec-zsts.md index f6d7278..5b97186 100644 --- a/src/vec/vec-zsts.md +++ b/src/vec/vec-zsts.md @@ -215,7 +215,7 @@ impl DoubleEndedIterator for RawValIter { And that's it. Iteration works! -One last thing that we need to take into account is that now when our vec gets dropped it deallocates the memory that was allocated during the time our vec was alive. With ZSTs we did not allocate any memory, and in fact we never do. So right now we have unsoundness in our code where we still try deallocate a `NonNull::dangling()` ptr that we use to simulate the ZST in our vec, This means that we would cause undefined behavior if we try to deallocate something that we never allocated (obviously and for the right reasons). To fix that, in our `RawVec` we are going to tweak our `Drop` trait and check that we deallocate only types that are sized. +One last thing we need to consider is that when our vector is dropped, it deallocates the memory that was allocated while it was alive. With ZSTs, we didn't allocate any memory; in fact, we never do. So, right now, our code has unsoundness: we're still trying to deallocate a `NonNull::dangling()` pointer that we use to simulate the ZST in our vector. This means we'd cause undefined behavior if we tried to deallocate something we never allocated (obviously, and for good reasons). To fix this, in our `RawVec`'s `Drop` trait, we're going to tweak it to ensure we only deallocate types that are sized. ```rust,ignore impl Drop for RawVec {