Skip to content

Change ZeroTrie to use sealed traits instead of macros #4561

Open
@sffc

Description

@sffc

Currently the ZeroTrie variants are generated using a macro, like this:

macro_rules! impl_zerotrie_subtype {
    ($name:ident, $iter_ty:ty, $iter_fn:path, $cnv_fn:path) => {
        impl<Store> $name<Store>
        where
        Store: AsRef<[u8]> + ?Sized,
        {
            /// Returns `true` if the trie is empty.
            #[inline]
            pub fn is_empty(&self) -> bool {
                self.store.as_ref().is_empty()
            }
        }
        #[cfg(feature = "alloc")]
        impl<Store> $name<Store>
        where
        Store: AsRef<[u8]> + ?Sized,
        {
            /// Returns an iterator over the key/value pairs in this trie.
            ///
            /// ✨ *Enabled with the `alloc` Cargo feature.*
            ///
            /// # Examples
            ///
            /// ```
            #[doc = concat!("use zerotrie::", stringify!($name), ";")]
            ///
            /// // A trie with two values: "abc" and "abcdef"
            #[doc = concat!("let trie: &", stringify!($name), "<[u8]> = ", stringify!($name), "::from_bytes(b\"abc\\x80def\\x81\");")]
            ///
            /// let mut it = trie.iter();
            /// assert_eq!(it.next(), Some(("abc".into(), 0)));
            /// assert_eq!(it.next(), Some(("abcdef".into(), 1)));
            /// assert_eq!(it.next(), None);
            /// ```
            #[inline]
            pub fn iter(&self) -> impl Iterator<Item = ($iter_ty, usize)> + '_ {
                 $iter_fn(self.as_bytes())
            }
        }
    }
}

It may be cleaner to instead use a sealed trait, like this:

struct Sealed;

pub trait ZeroTrieVariant : Sealed {
    #[internal] // #4467
    type IterTy;
    fn iter(...) -> ...;
}

pub struct AsciiOnly {
    _not_constructible: core::convert::Infallible,
}

impl ZeroTrieVariant for AsciiOnly { ... }

// Alias
pub type ZeroAsciiTrie<Store> = ZeroTrie<AsciiOnly, Store>;

pub struct ZeroTrie2<Variant, Store>
where
    Store: AsRef<[u8]> + ?Sized,
{
    /// Returns `true` if the trie is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.store.as_ref().is_empty()
    }
}

#[cfg(feature = "alloc")]
impl<Variant, Store> ZeroTrie2<Variant, Store>
where
    Variant: ZeroTrieVariant,
    Store: AsRef<[u8]> + ?Sized,
{
    /// Returns an iterator over the key/value pairs in this trie.
    ///
    /// ✨ *Enabled with the `alloc` Cargo feature.*
    ///
    /// # Examples
    ///
    /// ```
    /// use zerotrie::ZeroTrie2;
    ///
    /// // A trie with two values: "abc" and "abcdef"
    /// let trie: &ZeroTrie2<AsciiOnly, [u8]> = ZeroTrie2::from_bytes(b"abc\x80def\x81");
    ///
    /// let mut it = trie.iter();
    /// assert_eq!(it.next(), Some(("abc".into(), 0)));
    /// assert_eq!(it.next(), Some(("abcdef".into(), 1)));
    /// assert_eq!(it.next(), None);
    /// ```
    #[inline]
    pub fn iter(&self) -> impl Iterator<Item = (Variant::IterTy, usize)> + '_ {
         Variant::iter(self.as_bytes())
    }
}

Things that need to be bikeshed:

  1. trait ZeroTrieVariant
  2. Marker types that implement the trait:
    • pub struct AsciiOnly
    • pub struct AsciiIgnoreCase
    • pub struct WithBytes

Without additional feedback I will proceed with these names.

I would like to do this when there aren't big open PRs using ZeroTrie in order to avoid conflicts and churn.

Ok?

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-zerovecComponent: Yoke, ZeroVec, DataBakeneeds-approvalOne or more stakeholders need to approve proposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions