-
Notifications
You must be signed in to change notification settings - Fork 8
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
Associated Reference Types #1
Conversation
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.
Hi, thanks for the pull request! I think adding support for GATs is a great idea, however I've made a series of remarks before I can pull. In particular, I think I'd like to see a separate pull request for the Lock Traits
part.
/// Abstract collection. | ||
pub trait Collection { | ||
/// Type of references to items of the collection. | ||
type ItemRef<'a>: Deref<Target = Self::Item> |
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.
I there a reason why you choose to add the Self: 'a
where clause? Why not just
type ItemRef<'a>: 'a + Deref<Target = Self::Item>
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.
I there a reason why you choose to add the
Self: 'a
where clause? Why not justtype ItemRef<'a>: 'a + Deref<Target = Self::Item>
I chose to as Self: 'a
because any generics placed in the type must live as long as 'a
. 'a
in this context is unbounded, so it could be anything, which would require that generics placed in the type must live long as 'static
.
In theory, the bound Self::Item: 'a
would work fine, but in practice, some reference types may hold more references to generic types than just Self::Item
, e.g. DashMap
's reference type also holds a reference to the key.
The most correct solution would probably be to add another type for every reference, where implementors could provide all the generics their reference type uses, so we could add a bound Self::ItemRefGenerics: 'a
or something like that. But just adding Self: 'a
probably works just as well and is more elegant.
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.
I'm not sure to see what you mean. dashmap::mapref::one::Ref<'a, K, V, S>
has only one lifetime parameter here. If we take the Get
trait for instance with the get
function:
fn get<'a>(&'a self, key: T) -> Option<Self::ItemRef<'a>>;
The 'a
lifetime on self
should imply Self: 'a
. Moreover I think associated type lifetimes are implicitly bound by the implementor's lifetime right? Here is an example:
#![feature(generic_associated_types)]
pub trait Foo {
type T<'a>;
}
impl<'t, T> Foo for &'t T {
type T<'a> = Self;
}
This compiles even though I never explicitly specified Self: 'a
for Foo:T<'a>
. But it automatically derived &'t T: 'a
even though &'t T
is obviously not 'static
.
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.
In fact, following this reasoning I even wonder if specifying Deref<Target = Self::Item>
is enough, instead of 'a + Deref<Target = Self::Item>
...
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.
Hu... you are right, the Self: 'a
bound seems required when the implementor has type parameters. The implicit bound that seems to be automatically derived for Self
in my example is not automatically derived for the type parameters. How weird.
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.
No it is even weirder. In my example It doesn't work if we replace Self
with &'a Self
. So it can detect Self: 'a
but not &'a Self: 'a
? I feel like my example should not be accepted in the first place...
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.
My confusion has been resolved in the forum. We will need to wait for #87479 to make an actual decision here about the where Self: 'a
bound.
src/lib.rs
Outdated
/// Queryable collection. | ||
pub trait Get<T>: Collection { | ||
/// Returns a reference to the item stored behind the given key (if any). | ||
fn get(&self, key: T) -> Option<&Self::Item>; | ||
fn get<'a>(&'a self, key: T) -> Option<Self::ItemRef<'a>>; |
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.
You don't need to use an explicit lifetime here (and other similar functions) and instead use the placeholder lifetime '_
to make it less verbose:
fn get(&self, key: T) -> Option<Self::ItemRef<'_>>;
src/lib.rs
Outdated
} | ||
|
||
/// Guarded queryable collection. | ||
pub trait GetLock<T>: Get<T> { |
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.
So you want to define interfaces for concurrent data structures. I'm not a fan of using the term Lock
in the name of the trait since such data structure may be implemented without any lock. Maybe ConcurrentGet
(a bit long) or ConstGet
(maybe ambiguous) would be more appropriate? Anyway I think introducing concurrent traits is well beyond just adding GATs and should be part of another pull request so we can discuss these details.
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.
So you want to define interfaces for concurrent data structures. I'm not a fan of using the term
Lock
in the name of the trait since such data structure may be implemented without any lock. MaybeConcurrentGet
(a bit long) orConstGet
(maybe ambiguous) would be more appropriate? Anyway I think introducing concurrent traits is well beyond just adding GATs and should be part of another pull request so we can discuss these details.
Okay, I'll split this into two PRs, I'll keep this PR on GATs.
src/lib.rs
Outdated
} | ||
|
||
/// Mutable collection where new elements can be inserted. | ||
pub trait Insert: Collection { | ||
/// The output of the insertion function. | ||
type Output; | ||
type Output<'a>; |
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.
Do you have a motivation for adding a lifetime to the associated Output
types?
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.
Do you have a motivation for adding a lifetime to the associated
Output
types?
I added it in case some insert operations returns a reference to the data back or something; although I should add Self: 'a
here too.
Thinking about your proposition, I think I would prefer to break the pub trait Collection {
type Item;
}
pub trait CollectionRef: Collection {
type ItemRef<'a>: ...;
}
pub trait CollectionMut: Collection {
type ItemMut<'a>: ...;
} |
Agh I just pushed broken code because I didn't put the |
That is probably for the better, I'll add that too. |
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.
Almost there. There is still an issue with the example the slab impl, and then we must wait GATs stabilization...
/// Abstract collection. | ||
pub trait Collection { | ||
/// Type of references to items of the collection. | ||
type ItemRef<'a>: Deref<Target = Self::Item> |
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.
My confusion has been resolved in the forum. We will need to wait for #87479 to make an actual decision here about the where Self: 'a
bound.
@@ -100,6 +100,7 @@ | |||
//! | |||
//! - `slab` providing the `Slab` collection. | |||
//! - `smallvec` providing the `SmallVec` collection. | |||
#![feature(generic_associated_types)] |
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.
Since I would prefer for this crate to remain usable with stable rust, we'll need to wait for GATs stabilization and remove this before merging release.
@danii can you fix the |
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.
Looks fine to me. Thanks! I won't release it until GATs are stabilized though.
Okay, done, sorry I keep missing some things. 😅 |
@danii FIY I think I'm going to drop the lifetime parameter for the |
I have added different associated types for the reference types returned from trait methods such as
Get::get
, using generic associated types to enable patterns such as collections that return guarded references. I've done this in anticipation of Rust stabilizing GATs soon, as their blog post has led me to believe. To further emphasize the pattern of collections that are locks, I've addedLock
traits which provide the same interface as their non-Lock
counterparts, only behind an immutable reference rather than a mutable one. To showcase these new features I've also added implementations for the traits on structures in the popular dashmap crate.I'll ready this pull request if and when (hopefully just a matter of when) GATs are stabilized.