Skip to content

skiplist: Remove the 'static bounds#1238

Draft
taiki-e wants to merge 1 commit into
mainfrom
taiki-e/skiplist-non-static
Draft

skiplist: Remove the 'static bounds#1238
taiki-e wants to merge 1 commit into
mainfrom
taiki-e/skiplist-non-static

Conversation

@taiki-e

@taiki-e taiki-e commented Feb 22, 2026

Copy link
Copy Markdown
Member

Closes #205

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR removes the K: 'static / V: 'static bounds from SkipMap/SkipSet mutation APIs by switching SkipMap to pin against either the global collector (when K/V don’t need drop) or a per-map Collector plus per-thread handles.

Changes:

  • Remove 'static bounds from SkipMap/SkipSet and corresponding entry APIs.
  • Introduce per-SkipMap collector selection + per-thread pinning via thread_local::ThreadLocal.
  • Add regression tests demonstrating non-'static key/value usage compiles.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
crossbeam-skiplist/src/map.rs Adds local pinning (ThreadLocal handles) and removes 'static bounds from public map APIs.
crossbeam-skiplist/src/set.rs Removes 'static bounds for set APIs (delegates to SkipMap).
crossbeam-skiplist/src/base.rs Makes SkipList::{new,with_comparator} unsafe and removes 'static bounds from base methods; exposes collector to crate.
crossbeam-skiplist/Cargo.toml Adds optional thread_local dependency enabled by std.
crossbeam-skiplist/tests/map.rs Adds a non-'static compilation regression test.
crossbeam-skiplist/tests/set.rs Adds a non-'static compilation regression test.
crossbeam-skiplist/tests/base.rs Updates tests to call the now-unsafe SkipList::new safely via a helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 473 to 498
impl<K, V> SkipList<K, V> {
/// Returns a new, empty skip list.
pub fn new(collector: Collector) -> Self {
Self::with_comparator(collector, Default::default())
///
/// # Safety
///
/// If `mem::needs_drop::<K>` or/and `mem::needs_drop::<V>` is true,
/// `collector` must destroy garbage within the lifetime of `K` or/and `V`.
///
/// The easiest way to ensure this is to request `K: 'static` and `V: 'static` bounds.
pub unsafe fn new(collector: Collector) -> Self {
// SAFETY: the caller must uphold the safety contract.
unsafe { Self::with_comparator(collector, Default::default()) }
}
}

impl<K, V, C> SkipList<K, V, C> {
/// Returns a new, empty skip list using the given comparator.
pub fn with_comparator(collector: Collector, comparator: C) -> Self {
///
/// # Safety
///
/// If `mem::needs_drop::<K>` or/and `mem::needs_drop::<V>` is true,
/// `collector` must destroy garbage within the lifetime of `K` or/and `V`.
///
/// The easiest way to ensure this is to request `K: 'static` and `V: 'static` bounds.
pub unsafe fn with_comparator(collector: Collector, comparator: C) -> Self {
Self {

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

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

SkipList::new/with_comparator becoming unsafe is a public API breaking change (the type is re-exported from the crate). Consider providing a safe constructor that preserves the previous ergonomics (e.g., a safe new that requires K: 'static, V: 'static and/or !needs_drop::<K/V>()), and keep the new behavior behind an unsafe “unchecked” constructor. Also consider documenting this change prominently (e.g., in the changelog) since callers will now need unsafe just to construct a SkipList.

Copilot uses AI. Check for mistakes.
Comment thread crossbeam-skiplist/tests/map.rs Outdated
Comment thread crossbeam-skiplist/tests/set.rs
}

struct LocalHandle(epoch::LocalHandle);
// TODO

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

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

LocalHandle contains an epoch::LocalHandle but is given an unsafe impl Send with a // TODO marker and no soundness rationale. Since Send is an unsafe contract, this needs an explicit safety comment explaining why it’s safe to move/drop these handles across threads in this usage, or (preferably) avoid the unsafe impl (e.g., by using a handle type that is already Send / adjusting the storage strategy so the handle never needs to cross threads).

Suggested change
// TODO
// SAFETY:
// `LocalHandle` is only ever stored inside `ThreadLocal<LocalHandle>` (see the `handles`
// field of `SkipMap`). The `thread_local` crate ensures that each per-thread value is
// created, accessed, and dropped on the same thread that owns it. As a result, even
// though `LocalHandle` is marked as `Send` to satisfy `ThreadLocal`'s bounds, the
// wrapped `epoch::LocalHandle` is never moved to or used from a different thread than
// the one that created it, preserving the thread-affinity guarantees required by
// `epoch::LocalHandle`.

Copilot uses AI. Check for mistakes.
Comment thread crossbeam-skiplist/src/map.rs
@taiki-e taiki-e force-pushed the taiki-e/skiplist-non-static branch 2 times, most recently from 67c187a to a8e00f7 Compare February 22, 2026 12:30
@taiki-e taiki-e force-pushed the taiki-e/skiplist-non-static branch from a8e00f7 to 6207923 Compare February 22, 2026 12:37
@taiki-e taiki-e requested a review from Copilot February 22, 2026 12:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 29 to 32
pub struct SkipMap<K, V, C = BasicComparator> {
inner: base::SkipList<K, V, C>,
handles: ThreadLocal<LocalHandle>,
}

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

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

Field drop order is currently inner then handles, but handles stores LocalHandles registered with inner.collector. Dropping inner (and thus its Collector) before dropping handles can make dropping those LocalHandles invalid/UB. Fix by reordering fields so handles is declared before inner, or implement Drop (or use ManuallyDrop) to ensure handles are dropped before inner.

Copilot uses AI. Check for mistakes.
Comment thread crossbeam-skiplist/src/base.rs

/// The `Collector` associated with this skip list.
collector: Collector,
pub(crate) collector: Collector,

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

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

Making collector a pub(crate) field increases coupling and makes it easier to accidentally rely on internal invariants. Prefer keeping the field private and exposing a narrow pub(crate) accessor (e.g., fn collector(&self) -> &Collector) or a dedicated registration/pinning helper on SkipList so only the intended operations are possible.

Copilot uses AI. Check for mistakes.
Comment thread crossbeam-skiplist/tests/base.rs
Comment on lines +34 to 37
fn new_() {
new::<i32, i32>(epoch::default_collector().clone());
new::<String, Box<i32>>(epoch::default_collector().clone());
}

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

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

The helper new forces the test function to be named new_, which is a bit opaque. Consider renaming the helper to something more specific (e.g., new_skiplist_static) so the test can remain fn new() without the underscore.

Copilot uses AI. Check for mistakes.
Comment on lines +478 to +479
/// If `mem::needs_drop::<K>` or/and `mem::needs_drop::<V>` is true,
/// `collector` must destroy garbage within the lifetime of `K` or/and `V`.

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

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

In these doc comments, "or/and" is grammatically awkward. Consider changing to "and/or" (or rephrasing) for clarity.

Suggested change
/// If `mem::needs_drop::<K>` or/and `mem::needs_drop::<V>` is true,
/// `collector` must destroy garbage within the lifetime of `K` or/and `V`.
/// If `mem::needs_drop::<K>` or `mem::needs_drop::<V>` is true,
/// `collector` must destroy garbage within the lifetime of `K` and/or `V`.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

Remove the 'static bounds

2 participants