Skip to content

include note on variance and example#136246

Merged
bors merged 1 commit into
rust-lang:masterfrom
hkBst:patch-29
Feb 12, 2025
Merged

include note on variance and example#136246
bors merged 1 commit into
rust-lang:masterfrom
hkBst:patch-29

Conversation

@hkBst

@hkBst hkBst commented Jan 29, 2025

Copy link
Copy Markdown
Member

Fixes #89150

@rustbot

rustbot commented Jan 29, 2025

Copy link
Copy Markdown
Collaborator

r? @cuviper

rustbot has assigned @cuviper.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jan 29, 2025
@rust-log-analyzer

This comment has been minimized.

@uazu

uazu commented Jan 29, 2025

Copy link
Copy Markdown

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 Invariant type). I guess the type-id is just one symptom of this behaviour by Rust, but you could say that whilst the Rust language treats this casting as not that significant, type-id treats it as very significant and gives a completely different ID. So type-id exposes the behaviour, which is why it would be helpful to document it here.

@hkBst

hkBst commented Jan 29, 2025

Copy link
Copy Markdown
Member Author

The description makes good sense to me. Maybe mention that variance in Rust only applies to lifetimes (as I understand it).

AIUI it is parameter positions that can have variance. So for parameter position 1 in G<_> to be

  • covariant means sub < super implies, G<sub> < G<super>,
  • contravariant means sub < super implies, G<sub> > G<super> (less than sign gets flipped),
  • invariant means you that you cannot say anything about the subtyping relation between G<sub> and G<super> from the given information.

@hkBst

hkBst commented Jan 29, 2025

Copy link
Copy Markdown
Member Author

I guess the type-id is just one symptom of this behaviour by Rust, but you could say that whilst the Rust language treats this casting as not that significant, type-id treats it as very significant and gives a completely different ID. So type-id exposes the behaviour, which is why it would be helpful to document it here.

This is an interesting perspective, and I will need to think about it some more.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@danielhenrymantilla danielhenrymantilla left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Sub can be used wherever Super can", since this does not hold true when both are wrapped in an invariant type, so I have amended it as "values of type Sub can be used wherever Super is expected". I've also deëmphasized the as casts part, since it doesn't seem specially important here, when "Just Passing™" the value suffices 🙂

Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
Comment thread library/core/src/any.rs Outdated
@hkBst

hkBst commented Jan 30, 2025

Copy link
Copy Markdown
Member Author

Nice to see this pitfall documented! 🙏

Very happy to do it.

  • I have taken the initiative to make a drive-by review; mainly:

Hurray for initiative, and thanks for an excellent review.

  • added an introductory section to get to the point before illustrating with examples;

I like it, but would like to hear what other people think, otherwise I'll just add it soon.

  • avoided the expression "type Sub can be used wherever Super can", since this does not hold true when both are wrapped in an invariant type, so I have amended it as "values of type Sub can be used wherever Super is expected". I've also deëmphasized the as casts part, since it doesn't seem specially important here, when "Just Passing™" the value suffices 🙂

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.

@rust-log-analyzer

This comment has been minimized.

@hkBst hkBst force-pushed the patch-29 branch 3 times, most recently from aeaeb46 to 89cb05c Compare January 31, 2025 14:19
@hkBst

hkBst commented Jan 31, 2025

Copy link
Copy Markdown
Member Author

@danielhenrymantilla I've now incorporated all your suggestions with some slight changes/rewordings.

@cuviper

cuviper commented Jan 31, 2025

Copy link
Copy Markdown
Member

r? libs

@rustbot rustbot assigned ibraheemdev and unassigned cuviper Jan 31, 2025

@ibraheemdev ibraheemdev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread library/core/src/any.rs Outdated
Comment on lines +696 to +706
/// // `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);
/// }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// // `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);
/// }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread library/core/src/any.rs Outdated
Comment on lines +660 to +688
/// 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"));
/// }
/// }
/// }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just trimming down some lines.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 11, 2025
Fixes rust-lang#89150

Co-authored-by: Daniel Henry-Mantilla <daniel.henry.mantilla@gmail.com>
@hkBst

hkBst commented Feb 11, 2025

Copy link
Copy Markdown
Member Author

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.

Minimized the example without losing the intuition of the ring analogy.

@ibraheemdev

Copy link
Copy Markdown
Member

This looks good, thanks. @bors r+

@bors

bors commented Feb 11, 2025

Copy link
Copy Markdown
Collaborator

📌 Commit e279c4e has been approved by ibraheemdev

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 11, 2025
@fmease

fmease commented Feb 11, 2025

Copy link
Copy Markdown
Member

@bors rollup

@bors bors merged commit 052ebc6 into rust-lang:master Feb 12, 2025
@rustbot rustbot added this to the 1.86.0 milestone Feb 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeId documentation should mention gotcha re covariant subtypes

9 participants