include note on variance and example - #136246
Conversation
commented
Jan 29, 2025
This comment has been minimized.
This comment has been minimized.
commented
Jan 29, 2025
|
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.
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
commented
Jan 31, 2025
|
@danielhenrymantilla I've now incorporated all your suggestions with some slight changes/rewordings. |
commented
Jan 31, 2025
|
r? libs |
| /// // `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; |
Fixes rust-lang#89150 Co-authored-by: Daniel Henry-Mantilla <daniel.henry.mantilla@gmail.com>
commented
Feb 11, 2025
Minimized the example without losing the intuition of the ring analogy. |
commented
Feb 11, 2025
|
This looks good, thanks. @bors r+ |
commented
Feb 11, 2025
commented
Feb 11, 2025
|
@bors rollup |
Fixes #89150