Skip to content

Added SmallVec::push_mut andSmallVec::insert_mut - #406

Merged
alejandro-vaz merged 10 commits into
servo:v2from
mematthias:add_push_mut
Aug 26, 2026
Merged

Added SmallVec::push_mut andSmallVec::insert_mut#406
alejandro-vaz merged 10 commits into
servo:v2from
mematthias:add_push_mut

Conversation

@mematthias

@mematthias mematthias commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

The push_mut feature (rust-lang/rust#135974) has already been merged and is expected to be included in the Rust 1.95 stable release. For this reason, we should also add push_mut and insert_mut here.

@emilio emilio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to split the formatting into a separate (preliminary?) PR.

Comment thread src/lib.rs Outdated
@mematthias
mematthias requested a review from emilio March 31, 2026 18:09
emilio
emilio previously requested changes Apr 16, 2026

@emilio emilio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miri seems to be complaining.

@balt-dev

Copy link
Copy Markdown

miri seems to be getting confused on the provenance?

@mematthias
mematthias requested a review from emilio April 25, 2026 18:45
@balt-dev

Copy link
Copy Markdown

as the guy who did push_mut in std, i feel like the change just made kind of defeats the point? the whole point of push_mut is that you're not rederiving the pointer
i say the old version is fine, we just need some way to tell miri it's okay

@mematthias

Copy link
Copy Markdown
Contributor Author

@balt-dev While I understand the concerns raised, I am currently unable to resolve the Miri report. That said, this should not block us from shipping the feature. At the moment, users neither benefit from nor are harmed by it. But by making it available now, we ensure that once the optimization is in place, existing users can immediately take advantage of it without any disruption to their experience.

One possible (but dirty) solution would be to use set_len directly (inline) within the push_mut function:

#[inline]
pub fn push(&mut self, value: T) {
    _ = self.push_mut(value);
}

#[inline]
#[must_use]
pub fn push_mut(&mut self, value: T) -> &mut T {
    let len = self.len();
    if len == self.capacity() {
        self.reserve(1);
    }
    // SAFETY: both the input and output are within the allocation
    let ptr = unsafe { self.as_mut_ptr().add(len) };
    // SAFETY: we allocated enough space in case it wasn't enough, so the address is valid for
    // writes.
    unsafe { ptr.write(value) };

    // unsafe { self.set_len(len + 1) };
    {
        let new_len = len + 1;
        debug_assert!(new_len <= self.capacity());
        let on_heap = self.len.on_heap();
        self.len = TaggedLen::new(new_len, on_heap);
    }

    unsafe { &mut *ptr }
}

@alejandro-vaz

alejandro-vaz commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

@mematthias @balt-dev While I understand the concerns raised, I am currently unable to resolve the Miri report. That said, this should not block us from shipping the feature. At the moment, users neither benefit from nor are harmed by it. But by making it available now, we ensure that once the optimization is in place, existing users can immediately take advantage of it without any disruption to their experience.

One possible (but dirty) solution would be to use set_len directly (inline) within the push_mut function:

#[inline]
pub fn push(&mut self, value: T) {
    _ = self.push_mut(value);
}

#[inline]
#[must_use]
pub fn push_mut(&mut self, value: T) -> &mut T {
    let len = self.len();
    if len == self.capacity() {
        self.reserve(1);
    }
    // SAFETY: both the input and output are within the allocation
    let ptr = unsafe { self.as_mut_ptr().add(len) };
    // SAFETY: we allocated enough space in case it wasn't enough, so the address is valid for
    // writes.
    unsafe { ptr.write(value) };

    // unsafe { self.set_len(len + 1) };
    {
        let new_len = len + 1;
        debug_assert!(new_len <= self.capacity());
        let on_heap = self.len.on_heap();
        self.len = TaggedLen::new(new_len, on_heap);
    }

    unsafe { &mut *ptr }
}

yeah

when set_len is called it takes a mutable reference into the full self and then miri can't prove that the reference is still pointing to valid data because the location it points to might have been mutated since and thus it might be uninitialized data (it might have been freed => returns null pointer to user => use after free). even though that isn't what happens in our code in any case

the TaggedLen / set_len model is not a very good abstraction for what we are trying to do

I honestly support setting the length inline with self.len = /*...*/

the TaggedLen thing should probably be redesigned anyway to be honest

but before hacking it we should change how length behaves, then we implement it properly

@mematthias

Copy link
Copy Markdown
Contributor Author

@alejandro-vaz Should the same dirty workaround be used for insert_mut?

@alejandro-vaz

Copy link
Copy Markdown
Collaborator

yeah

the set_len method doesn't make much sense and just confuses the borrow checker by taking a mutable reference of the whole smallvec instead of just its len field

@alejandro-vaz

Copy link
Copy Markdown
Collaborator

please rebase the commit history onto servo:v2 to remove merge commits and have a linear history

Comment thread src/lib.rs Outdated
Comment thread src/lib.rs
Comment on lines +1226 to +1235
{
// This block is an exact copy of `self.set_len`.
// We have to do this so that Miri doesn't report a "Stacked Borrows" rule violation.
// See PR/406

let new_len = len + 1;
debug_assert!(new_len <= self.capacity());
let on_heap = self.len.on_heap();
self.len = TaggedLen::new(new_len, on_heap);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this is good for now until we implement #451

Signed-off-by: Alejandro Vaz <alejandro.vaz.myt@gmail.com>
@alejandro-vaz

Copy link
Copy Markdown
Collaborator

@mematthias I had to merge upstream changes, it annoyed rustfmt

please run cargo fmt --all so I can approve it

@alejandro-vaz
alejandro-vaz removed the request for review from emilio August 26, 2026 20:44
@alejandro-vaz
alejandro-vaz dismissed emilio’s stale review August 26, 2026 20:45

old review, changes already implemented

@alejandro-vaz
alejandro-vaz added this pull request to the merge queue Aug 26, 2026
Merged via the queue into servo:v2 with commit 9863629 Aug 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants