include note on variance and example#136246
Conversation
This comment has been minimized.
This comment has been minimized.
|
The description makes good sense to me. Maybe mention that variance in Rust only applies to lifetimes (as I understand it). I wonder whether the longer example is too specific and might not make sense without the motivation of the original code. We can see how it looks to the reviewer, coming at this fresh. Certainly it illustrates the problem with a practical example, very similar to my particular problem case. However, really the essence of the problem is that the type-id of the generic type argument can be changed by the caller from one method call to another on the same instance of a struct. So the struct implementation can't count on that type-id being constant over the lifetime of the instance without further precautions (e.g. using the |
AIUI it is parameter positions that can have variance. So for parameter position 1 in G<_> to be
|
This is an interesting perspective, and I will need to think about it some more. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
danielhenrymantilla
left a comment
There was a problem hiding this comment.
Nice to see this pitfall documented! 🙏
- I have taken the initiative to make a drive-by review; mainly:
- added an introductory section to get to the point before illustrating with examples;
- used some markdown headers and new lines to make it less densely packed;
- avoided the expression "type
Subcan be used whereverSupercan", since this does not hold true when both are wrapped in an invariant type, so I have amended it as "values of typeSubcan be used whereverSuperis expected". I've also deëmphasized theascasts part, since it doesn't seem specially important here, when "Just Passing™" the value suffices 🙂
Very happy to do it.
Hurray for initiative, and thanks for an excellent review.
I like it, but would like to hear what other people think, otherwise I'll just add it soon.
My version was dead wrong, so thanks for catching this, and I also really like the silent casting without even using "as". It is very sneaky and really shows how easily you could almost accidentally mix up types. |
This comment has been minimized.
This comment has been minimized.
aeaeb46 to
89cb05c
Compare
|
@danielhenrymantilla I've now incorporated all your suggestions with some slight changes/rewordings. |
|
r? libs |
ibraheemdev
left a comment
There was a problem hiding this comment.
I like this. The example is a little long but I think that's fine. I left a couple of comments to minimize the example a little bit, but other than that this looks good.
| /// // `FnRef` is a subtype of `FnStaticRef`. | ||
| /// // Both are 'static, and thus have a TypeId. | ||
| /// type FnRef = fn(&()); | ||
| /// type FnStaticRef = fn(&'static ()); | ||
| /// | ||
| /// fn main() { | ||
| /// type TheOneRing = FnStaticRef; | ||
| /// | ||
| /// let the_one_ring: Unique<TheOneRing> = Unique::new().unwrap(); | ||
| /// assert_eq!(Unique::<TheOneRing>::new(), None); | ||
| /// | ||
| /// type OtherRing = FnRef; | ||
| /// | ||
| /// let other_ring: Unique<OtherRing> = Unique::new().unwrap(); | ||
| /// // Use that `Unique<OtherRing>` is a subtype of `Unique<TheOneRing>` 🚨 | ||
| /// let fake_one_ring: Unique<TheOneRing> = other_ring; | ||
| /// assert_eq!(fake_one_ring, the_one_ring); | ||
| /// | ||
| /// std::mem::forget(fake_one_ring); | ||
| /// } |
There was a problem hiding this comment.
| /// // `FnRef` is a subtype of `FnStaticRef`. | |
| /// // Both are 'static, and thus have a TypeId. | |
| /// type FnRef = fn(&()); | |
| /// type FnStaticRef = fn(&'static ()); | |
| /// | |
| /// fn main() { | |
| /// type TheOneRing = FnStaticRef; | |
| /// | |
| /// let the_one_ring: Unique<TheOneRing> = Unique::new().unwrap(); | |
| /// assert_eq!(Unique::<TheOneRing>::new(), None); | |
| /// | |
| /// type OtherRing = FnRef; | |
| /// | |
| /// let other_ring: Unique<OtherRing> = Unique::new().unwrap(); | |
| /// // Use that `Unique<OtherRing>` is a subtype of `Unique<TheOneRing>` 🚨 | |
| /// let fake_one_ring: Unique<TheOneRing> = other_ring; | |
| /// assert_eq!(fake_one_ring, the_one_ring); | |
| /// | |
| /// std::mem::forget(fake_one_ring); | |
| /// } | |
| /// // `FnRef` is a subtype of `FnStaticRef`. Both are 'static, and thus have a `TypeId`. | |
| /// type FnRef = fn(&()); | |
| /// type FnStaticRef = fn(&'static ()); | |
| /// | |
| /// fn main() { | |
| /// let unique_static_ref: Unique<FnStaticRef> = Unique::new().unwrap(); | |
| /// assert_eq!(Unique::<FnStaticRef>::new(), None); | |
| /// | |
| /// let other_ref: Unique<FnRef> = Unique::new().unwrap(); | |
| /// // Use that `Unique<FnRef>` is a subtype of `Unique<FnStaticRef>` 🚨 | |
| /// let fake_unique_static_ref: Unique<FnStaticRef> = other_ref; | |
| /// assert_eq!(fake_unique_static_ref, unique_static_ref); | |
| /// | |
| /// std::mem::forget(fake_unique_static_ref); | |
| /// } |
There was a problem hiding this comment.
I like the one ring analogy, but I feel it is cleaner to stick with the original type names (also trims the line count a bit).
| /// use std::any::TypeId; | ||
| /// use std::collections::BTreeSet; | ||
| /// use std::marker::PhantomData; | ||
| /// use std::sync::Mutex; | ||
| /// | ||
| /// use unique::Unique; | ||
| /// | ||
| /// mod unique { | ||
| /// use super::*; | ||
| /// | ||
| /// static ID_SET: Mutex<BTreeSet<TypeId>> = Mutex::new(BTreeSet::new()); | ||
| /// | ||
| /// // Due to its private data member, outside this module, | ||
| /// // this struct can only be created using `new`. | ||
| /// #[derive(Debug, PartialEq)] | ||
| /// pub struct Unique<TypeAsId: 'static>( | ||
| /// // this usage of `TypeAsId` makes `Unique` covariant 🚨 | ||
| /// PhantomData<TypeAsId>, | ||
| /// ); | ||
| /// | ||
| /// impl<TypeAsId: 'static> Unique<TypeAsId> { | ||
| /// pub fn new() -> Option<Self> { | ||
| /// let mut set = ID_SET.lock().unwrap(); | ||
| /// set.insert(TypeId::of::<TypeAsId>()) | ||
| /// .then(|| Self(PhantomData)) | ||
| /// } | ||
| /// } | ||
| /// | ||
| /// impl<TypeAsId: 'static> Drop for Unique<TypeAsId> { | ||
| /// fn drop(&mut self) { | ||
| /// let mut set = ID_SET.lock().unwrap(); | ||
| /// (!set.remove(&TypeId::of::<TypeAsId>())).then(|| panic!("duplicity detected")); | ||
| /// } | ||
| /// } | ||
| /// } |
There was a problem hiding this comment.
| /// use std::any::TypeId; | |
| /// use std::collections::BTreeSet; | |
| /// use std::marker::PhantomData; | |
| /// use std::sync::Mutex; | |
| /// | |
| /// use unique::Unique; | |
| /// | |
| /// mod unique { | |
| /// use super::*; | |
| /// | |
| /// static ID_SET: Mutex<BTreeSet<TypeId>> = Mutex::new(BTreeSet::new()); | |
| /// | |
| /// // Due to its private data member, outside this module, | |
| /// // this struct can only be created using `new`. | |
| /// #[derive(Debug, PartialEq)] | |
| /// pub struct Unique<TypeAsId: 'static>( | |
| /// // this usage of `TypeAsId` makes `Unique` covariant 🚨 | |
| /// PhantomData<TypeAsId>, | |
| /// ); | |
| /// | |
| /// impl<TypeAsId: 'static> Unique<TypeAsId> { | |
| /// pub fn new() -> Option<Self> { | |
| /// let mut set = ID_SET.lock().unwrap(); | |
| /// set.insert(TypeId::of::<TypeAsId>()) | |
| /// .then(|| Self(PhantomData)) | |
| /// } | |
| /// } | |
| /// | |
| /// impl<TypeAsId: 'static> Drop for Unique<TypeAsId> { | |
| /// fn drop(&mut self) { | |
| /// let mut set = ID_SET.lock().unwrap(); | |
| /// (!set.remove(&TypeId::of::<TypeAsId>())).then(|| panic!("duplicity detected")); | |
| /// } | |
| /// } | |
| /// } | |
| /// mod unique { | |
| /// use std::any::TypeId; | |
| /// use std::collections::BTreeSet; | |
| /// use std::marker::PhantomData; | |
| /// use std::sync::Mutex; | |
| /// | |
| /// static ID_SET: Mutex<BTreeSet<TypeId>> = Mutex::new(BTreeSet::new()); | |
| /// | |
| /// // Due to its private field, this struct can only be created using `new` outside this module. | |
| /// #[derive(Debug, PartialEq)] | |
| /// pub struct Unique<TypeAsId: 'static>( | |
| /// // this usage of `TypeAsId` makes `Unique` covariant 🚨 | |
| /// PhantomData<TypeAsId>, | |
| /// ); | |
| /// | |
| /// impl<TypeAsId: 'static> Unique<TypeAsId> { | |
| /// pub fn new() -> Option<Self> { | |
| /// let mut set = ID_SET.lock().unwrap(); | |
| /// set.insert(TypeId::of::<TypeAsId>()).then(|| Self(PhantomData)) | |
| /// } | |
| /// } | |
| /// | |
| /// impl<TypeAsId: 'static> Drop for Unique<TypeAsId> { | |
| /// fn drop(&mut self) { | |
| /// let mut set = ID_SET.lock().unwrap(); | |
| /// (!set.remove(&TypeId::of::<TypeAsId>())).then(|| panic!("duplicity detected")); | |
| /// } | |
| /// } | |
| /// } | |
| /// | |
| /// use unique::Unique; |
There was a problem hiding this comment.
Just trimming down some lines.
Fixes rust-lang#89150 Co-authored-by: Daniel Henry-Mantilla <daniel.henry.mantilla@gmail.com>
Minimized the example without losing the intuition of the ring analogy. |
|
This looks good, thanks. @bors r+ |
|
@bors rollup |
Fixes #89150