-
Notifications
You must be signed in to change notification settings - Fork 10
Update insert
, contains
and remove
methods to use Borrow
trait
#17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Thank you for you PR and the detailed explanation. pub fn insert<U>(&mut self, item: &U)
where U: PartialEq<T> + Hash + ?Sized { |
Alas, that also pulls in the hidden T<'life_of_filter> lifetime. Here's my tiny example to test compilation
|
Thank you for sharing the example code. // Define a struct and implement `PartialEq` for the desired reference type.
#[derive(Hash)]
struct MyTuple(String);
impl PartialEq<(&String,)> for MyTuple {
fn eq(&self, rhs: &(&String,)) -> bool {
self.0 == *rhs.0
}
}
// Added `::<MyTuple>`
let mut f = scalable_cuckoo_filter::ScalableCuckooFilter::<MyTuple>::new(1000, 0.05);
// The remaining code is identical to yours.
for _ in 0..2 {
let a = "hello".to_string();
let q = (&a,);
f.insert(&q);
dbg!(f.contains(&q));
f.insert(&q);
} And the patch that was applied locally to this crate is shown below: diff --git a/src/scalable_cuckoo_filter.rs b/src/scalable_cuckoo_filter.rs
index d4338de..88e9803 100644
--- a/src/scalable_cuckoo_filter.rs
+++ b/src/scalable_cuckoo_filter.rs
@@ -208,7 +208,11 @@ impl<T: Hash + ?Sized, H: Hasher + Clone, R: Rng> ScalableCuckooFilter<T, H, R>
}
/// Returns `true` if this filter may contain `item`, otherwise `false`.
- pub fn contains(&self, item: &T) -> bool {
+ pub fn contains<U>(&self, item: &U) -> bool
+ where
+ T: PartialEq<U>,
+ U: Hash + ?Sized,
+ {
let item_hash = crate::hash(&self.hasher, item);
self.filters
.iter()
@@ -242,7 +246,11 @@ impl<T: Hash + ?Sized, H: Hasher + Clone, R: Rng> ScalableCuckooFilter<T, H, R>
/// }
/// }
/// ```
- pub fn insert(&mut self, item: &T) {
+ pub fn insert<U>(&mut self, item: &U)
+ where
+ T: PartialEq<U>,
+ U: Hash + ?Sized,
+ {
let item_hash = crate::hash(&self.hasher, item);
let last = self.filters.len() - 1;
self.filters[last].insert(&self.hasher, &mut self.rng, item_hash); |
I think that my approach is not perfect (e.g., it is a breaking change introducing an additional |
That essentially works, but I think (ab)using PartialEq isn't the right call. It also works with a custom, function less trait, but it's still a breaking change having the user implement it. Unfortunately, a blanket implementation of that trait again brings us into the situation where a SCF will accept any Hash+?Sized, not just T. pub trait FixReferences<T: ?Sized> {}
impl <T, U> FixReferences<T> for U where U: ?Sized + Hash {} so baring that, the user has to impl scalable_cuckoo_filter::FixReferences<MyTuple<'_>> for MyTuple<'_> { } |
I agree. I came up with a better approach that uses fn main() {
#[derive(Hash)]
struct MyTuple((&'static String,));
impl<'a> std::borrow::Borrow<(&'a String,)> for MyTuple {
fn borrow(&self) -> &(&'a String,) {
&self.0
}
}
let mut f = scalable_cuckoo_filter::ScalableCuckooFilter::<MyTuple>::new(1000, 0.05);
for _ in 0..2 {
let a = "hello".to_string();
let q = (&a,);
f.insert(&q);
dbg!(f.contains(&q));
f.insert(&q);
}
} diff --git a/src/scalable_cuckoo_filter.rs b/src/scalable_cuckoo_filter.rs
index d4338de..1bd4385 100644
--- a/src/scalable_cuckoo_filter.rs
+++ b/src/scalable_cuckoo_filter.rs
@@ -208,7 +208,11 @@ impl<T: Hash + ?Sized, H: Hasher + Clone, R: Rng> ScalableCuckooFilter<T, H, R>
}
/// Returns `true` if this filter may contain `item`, otherwise `false`.
- pub fn contains(&self, item: &T) -> bool {
+ pub fn contains<U>(&self, item: &U) -> bool
+ where
+ T: std::borrow::Borrow<U>,
+ U: Hash + ?Sized,
+ {
let item_hash = crate::hash(&self.hasher, item);
self.filters
.iter()
@@ -242,7 +246,11 @@ impl<T: Hash + ?Sized, H: Hasher + Clone, R: Rng> ScalableCuckooFilter<T, H, R>
/// }
/// }
/// ```
- pub fn insert(&mut self, item: &T) {
+ pub fn insert<U>(&mut self, item: &U)
+ where
+ T: std::borrow::Borrow<U>,
+ U: Hash + ?Sized,
+ {
let item_hash = crate::hash(&self.hasher, item);
let last = self.filters.len() - 1;
self.filters[last].insert(&self.hasher, &mut self.rng, item_hash); This approach is still somewhat tricky, but it seems acceptable to me. |
I guess we could supplement a bunch of default impls
So I guess we either
Thoughts:
If I could say edit:
But it's not quite right. Now ScalableCuckooFilter:: |
Thank you for your additional comment. As I said before, I want to avoid using Among the options, I prefer option |
Thoughts:
You can make the anonymous type explicit:
I think we might have a winner. |
It's still a breaking change though, as the test cases show. It 'sabotages' the type interference from the first insert call. |
e095d30
to
e69b67a
Compare
I've adjusted the PR for the borrow solution, fixed up the test cases and added some brief documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
insert
, contains
and remove
methods to use Borrow
traitI
insert
, contains
and remove
methods to use Borrow
traitIinsert
, contains
and remove
methods to use Borrow
trait
Thank you! This felt very much like a collaboration, and not the random drive by PR that's so common. |
I've really enjoyed scalable_cuckoo_filter, thank you!
I need to deduplicate a
(&[u8], Option<&[u8]>, Option<&[u8]>, Option<&[u8]>,)
and I don't want to do cloning just to get a&[u8]
(millions of entries...).Unfortunately, declaring a
ScalableCuckooFilter<MyTuple>
and
MyTuple<'a> = (&'a [u8], Option<&'a [u8], ...)
makes rustc think that insert() keeps the reference around.
The problem is that rust ties the inner references to the struct's lifetime, even if you introduce a lifetime parameter and change the
PhantomData<T>
to aPhantomData<&'b T>
.I could instead have a
ScalableCuckooFilter<T = u64>
,and pass in a hash of
MyTuple
, which then get's immediately hashed again, but it's hard to envision if that would still get the cuckoo's guarantees.One could remove T from the
ScaleableCuckooFilter
, and put the constraints on query/insert - but that would allow you to mix types in one filter. I don't think that'd be sensible.Or we can export
crate::hash
, makehasher
onScalableCuckoFilter
pub, and introduce anunsafe insert_by_hash(&mut self, item_hash: u64)
function.Or, combining the ideas, adding this to ScalableCuckooFilter:
pub unsafe fn insert_reference_type<U: Hash + ?Sized>(&mut self, item: &U)
(which this PR does).
That does not require the user to know anything about the hash,
just to uphold the type invariant.
(Seems rust's been missing a type equality constraint for 10+ years now:
rust-lang/rust#20041 )