-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: IndexGet and IndexSet #2953
Conversation
Additional benefits include more support for DSLs similar to nmigen and its use of Python's index operator to indicate bit slicing. nmigen is based on using Python's operators to construct a description of what the outputted hardware will do rather than executing the operations directly as a normal programming language might. |
Co-authored-by: kennytm <[email protected]>
It's a little sad that "set" as a verb or noun has such different meanings, so your |
I also think that As far as Hear me out -- bitset and arbitrary bit slicing seems like a good candidate for this, as you can't just reference an arbitrary bit slice. But what if you could, and the bit offset were stored in the value as a fat pointer? In most cases, if you were directly copying the data out, this would be mostly optimised away, but otherwise you'd be able to do everything as usual. If it's copying or cloning data out, it also makes sense to just require a method call or dereference. If we were moving out, I'd see the need for an explicit trait, but that seems a bit weird and magical IMHO. For example, if Basically, IMHO, we should ditch IndexGet. |
|
I feel like what fn index(&self, x: u8) -> &bool {
static TRUE: bool = true;
static BOOL: bool = false;
if self.foo(x) { &TRUE } else { &FALSE }
} (You could probably use const propagation instead of explicit statics, but w/e) Still, this does seem unnecessary -- I thought the plan for a while has been to add an |
We should definitely explore the custom fat pointer idea before doing this. In fact, we could implement such fat pointers without necessarily touching rustc by using ATCs that default to references:
We'd provide the bitvec functionality by returning a guard that tracks an offset, which then work with Aside from likely being suboptimal, I'm afraid this I think this ATC solution however avoids massive confusion about which traits handle which functionality, which keeps the language simpler and more accessible. I think even code that needs strange guards like bitvec and hashmap becomes much more comprehensible with the ATC solution. Any code without strange guards becomes no more complex. We could explore using similar ATCs to avoid trait proliferation elsewhere too, maybe becoming a common easily understandable design pattern within std. |
We've currently no "assignment" trait really. Would one make sense?
An ATC solution still handles |
I really like the ATC solution, but it breaks backwards compatibility. If your concern is with the teachability of IndexGet, perhaps making it mutually exclusive with Index would alleviate some of the mental burden? |
How does the ATC solution break backwards compatibility? AFAICT it's still the same if |
In the end, index notation for bitsets and hashmap sounds nice, but should not be considered too important really, certainly not important enough to make reading code harder. An unambiguous notation like Yes, I noticed the ATC solution would "break" generic types that delegate Also, any generic types that delegate Am I missing another breakage? |
One use case I've wanted, which might be similar to bit sets, is a way to "index" into a 2d slice (eg: image data) and get a 2d sub-slice. I'm not sure if this would need to change design to support that, but I've also been told that we'd probably need some custom DST stuff in the language as well to make anything like that be practical. |
Well, code like this would require authors to specify another type bound, as shown here. I'd assume making the traits exclusive (if we were to go down that path) would be compiler magic, but I'm not sure what you mean by |
Interesting, the ATC trick breaks any code that uses explicit I'd suspect any solution involving exclusive traits would be rejected just because rust worked so hard to avoid those in the past, so maybe we should be discussing the I actually think two dimensional indexing sounds more valuable than indexing for bitsets or hashmap. If we'd some
Amusingly, one could already make bitset work using existing
We cannot play exactly this trick for
In any case, there was considerable discussion and RFCs around doing |
What would your IndexGuard trait look like? I'm also uncertain of how your CrazyEntry would let map[key] = value work, which is one of the main points of this proposal. Could you provide a playground link of what you mean? The main issue is that none of the existing Index traits use GATs, which kind of preclude 2d subslicing. I think if we had a design with GATs (this was hastily sketched up) we could support it, but that would basically be a complete overhaul of how indexing works right now. The main reason I don't want to just add The more I think about it though, |
I think today, |
Yes, I only proposed I no longer think Edit: We'd want |
If Rust had associated types with generic lifetime parameters, the impl<T: Index<Idx>, Idx> IndexGet<Idx> for T {
type Output<'a> = &'a <T as Index<Idx>>::Output;
fn index_get(&'a self, idx: Idx) -> Self::Output<'a> {
self.index(idx)
}
} You can do the same for Feel free to point out all the problems with this approach. I will admit I didn't read all of the proposal before commenting this, but I didn't see any mention during a skim. |
We dropped another question up thread: Is doing an assignment with It appears no because it does not propagate errors correctly. It's maybe best not to give users a "more ergonomic" option that discourages error handling. I felt |
I mean I've backtracked on my claim that we would need to GAT the rest of the traits, a simple GAT in the associated type of |
If I just write |
I dunno, I think that assigning index is pretty idiomatic in a lot of cases. If you want to cover all cases, something like |
So after looking at #159's Alternatives block, I came up with this design that should solve most of @burdges concerns regarding mutually exclusive traits as well as compelx reasoning. It's use of a GAT also makes 2d subslicing possible, but it comes with a plethora of |
We get around complex reasoning by making the desugaring obvious. I think we could even propose the |
How does the compiler decide between the methods in |
|
Is |
No? Well, if Self::Output is |
Wait, but something like |
There would be an blanket forwarding impl for IndexSet. I took a stab at IndexGet and imo it's not possible to implement it without exclusive traits or the overload mechanism in this RFC. |
I still don't quite understand the motivation on why |
@pickfire Some examples are in the RFC, but it's for cases where returning a reference is impossible. |
At this point your rules ensure that any existing code still compiles with the existing reference methods, right? If not, then we should watch for performance regressions due to using moves over copies? At the extreme one might consider |
Yup, without any user-side changes everything will desguar identically. |
So |
Yes. In principle, we might exploit that either to avoid the if-else trait matching logic, and/or for fancier interfaces, ala
We've discussed such options relatively well here so far, although a bit more exploration maybe warranted. All these ideas incur some soundness risks too because they interact with unsafe code everywhere. At first blush, I'd think the if-else trait matching logic incurs less soundness risks than using ATCs, which probably still invoke some even more flexible form of that logic on the ATC itself, but unclear. |
We could explore the custom fat pointer suggestion by @clarfon slightly more.. I showed up thread in #2953 (comment) that bitsets could work without
I think const generics can encode any index at the type level so long as it is foreseeable at compile time, but that last restriction prevents doing this trick for hashmap. At the same time, we do risk a massive code size explosion from const generics being used with trait objects, which might require expanding our fat pointer repertoire eventually. In other words, should/does rust reject making a trait object from
I'd some thoughts about cc #1403 |
One problem with this proposal is this bakes into IndexSet that Rust does not allow assignments to I suspect you've read #2594 and all of it's precursors, some of which sound a lot like what you're trying to do. |
FWIW I'm in favour of having assignment be a method on |
One thing I've still been debating is the GAT on IndexGet. Is there a purpose for the GAT that would not be better served by #2594? @burdges looking for your thoughts on this. The main thjng it would allow you to do is return a reference to &self, but the lack of proper mut support do hamper it, GATs are still a ways away, and custom DSTs are arguably the better move. I guess the main question is does a GAT here open up anything custok DSTs won't? |
This RFC adds
IndexGet
andIndexSet
types, which allows for additional options when implementing containers, and changing the behavior of assignments.Rendered