How can I compare objects only when they are created from the same parent object?

I want to compare Obj only when they are created from the same Parent object, and this works, but only in one way: if you change the order of comparison, it is not.

Here is the minimum code:

 use std::marker::PhantomData; struct Parent { val: u64, } impl Parent { pub fn new(v: u64) -> Parent { Parent { val: v } } pub fn child(&self, v: u64) -> Child { Child { val: v, phantom: PhantomData, } } } struct Child<'a> { val: u64, phantom: PhantomData<&'a Parent>, } impl<'a> Child<'a> { pub fn compare(&'a self, l: &Obj<'a>, r: &Obj<'a>) -> bool { l.val == r.val } pub fn obj(&'a self, v: u64) -> Obj<'a> { Obj { val: v, child: self, } } } struct Obj<'a> { val: u64, child: &'a Child<'a>, } impl<'a> PartialEq<Obj<'a>> for Obj<'a> { fn eq(&self, other: &Obj<'a>) -> bool { self.child.compare(self, other) } } #[test] fn test() { let parent = Parent::new(1); let child = parent.child(2); let obj1 = child.obj(3); let obj2 = child.obj(3); // those are from the same parent, this sould work (and works). assert!(obj1 == obj2); assert!(obj2 == obj1); let parent2 = Parent::new(1); let child2 = parent2.child(2); let obj12 = child2.obj(3); let obj22 = child2.obj(3); // this works fine too assert!(obj12 == obj22); assert!(obj22 == obj12); // those are from different parents //assert!(obj1 == obj12); // that line DOES NOT compile, which is exactly what I want! assert!(obj12 == obj1); // but that line suddenly DOES compile. } 

How can I change the code so that the last line does not compile?

+6
source share
2 answers

I will gladly explain the approach for life, but it does not seem viable.

When is X subtype of Y (denoted by X <: Y )?

The question asked for generics includes variance .

Dispersion answers the question: for the generic type G<X> value X <: Y means the ratio of G<X> to G<Y> .

  • Covariance: X <: Y => G<X> <: G<Y>
  • Invariance: X == Y => G<X> <: G<Y>
  • Contravariance: X <: Y => G<Y> <: G<X>

Cell<X> is invariant wrt X , therefore phantom: PhantomData<Cell<&'a Parent>>, makes Child<'a> invariant wrt 'a .

PhantomData is a way to trick you into talking about variance by simply describing it in the types that you already know.

This works, but not so fast, because we can create a situation where the lifetimes are completely equal, and then the test is compiled again!

 let (parent, parent2) = (Parent::new(1), Parent::new(1)); let (child, child2) = (parent.child(2), parent2.child(2)); // Plan is foiled!! 
+3
source

Here is an answer that can excite you or scare you: use monads.

In particular, ST Monad . Unfortunately, I can’t explain it to you, but I understand that it can be useful in this case. Let us know if you find out!

This one was shown to me for my own project :

You can replicate InvariantLifetime, which is actually done in the BTreeMap implementation . The reason I say this is a very heavy weight is because the only way to make its properties useful is to require that each document be created (or at least accessible) within the closure.

A pragmatic solution is not to try to compile these compile-time errors, but simply include the parent pointer in the comparison:

 #[derive(Debug,Copy,Clone,PartialEq)] struct Parent { val: u64, } impl Parent { fn child(&self) -> Child { Child { parent: self, val: self.val } } } #[derive(Debug,Copy,Clone)] struct Child<'a> { parent: &'a Parent, val: u64, } impl<'a> PartialEq for Child<'a> { fn eq(&self, other: &Child<'a>) -> bool { (self.parent as *const _, self.val) == (other.parent as *const _, other.val) } } fn main() { let (p1, p2) = (Parent { val: 42 }, Parent { val: 42 }); let p1_c1 = p1.child(); let p1_c2 = p1.child(); let p2_c1 = p2.child(); println!("{}", p1_c1 == p1_c2); println!("{}", p1_c1 == p2_c1); } 
+2
source

Source: https://habr.com/ru/post/988162/


All Articles