-
Notifications
You must be signed in to change notification settings - Fork 22
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
A new architecture #11
Comments
The advantages to the new architecture:
|
I started working on this in #12 |
I think possibly I take advantage of the unique features of each ownership type in some places. For example, QCell has a QCellOwnerID, so it doesn't need to have direct access to the owner to create a cell. This happens to be useful when the borrow checker makes it hard to get references to everything you need all at once. For the other types, there is no need at all for the owner to be accessible to create a cell. Also, I'm not totally sure how things are going to work out with the lifetimes and marker types with this approach. I found the ways that worked well mostly in the traditional Rust style of trying all the things that didn't work first! I can't say for sure how this approach will work out, so I think I'll just wait to see how you get on with it. |
We can create wrapper functions that put this functionality back, this is just swapping out the core. Nothing I have proposed here should break any existing code. I have now created a working prototype, look in the |
Okay, I'm getting my head around this. I see the idea now. A few comments: I find the term "proxy" confusing. I know (now) that this is a type containing/embodying a proxy for the ownership, i.e. the integer, marker type or lifetime, but "proxy" on its own in the source doesn't make a lot of sense. Perhaps "marker" would work better. You've lost the free list for IDs for QCell. That was important. Imagine the case where a function allocates an owner for temporary working storage of cells whilst processing and then frees everything up and releases the owner before returning. With the current implementation you've limited the use of this function to 2^32 calls. (I originally had the You've renamed a lot of stuff internally, e.g. SingletonOwner, but it looks like this leaks out to the crate user, e.g. in panic messages. So assuming we stick to the current naming in the public interface, that will cause confusion. It would be better to keep naming consistent, even if perhaps you'd prefer your naming. I think I'm mostly okay with this approach. It does bring common things together in the code without adding too much type machinery to achieve it. However, there's a downside in that it complicates things for the crate user because I think probably they'll be seeing ValueCell in arguments and compiler messages instead of the cell type they were expecting, but assuming the marker types are named in a recognisable way, that should probably still be understandable enough. I'll need to look at how many internal types are getting exposed in the API now, though, because I'd prefer the exposed API to be minimal. Really totally minimal would be my preference, but that's probably not going to be possible with this approach. I will think some more tomorrow. |
That's fine, I'm not too concerned about nomenclature
We could easily increase the size to This new method has two benefits, it is now significantly cheaper to create a
Yes I remember commenting on that in users a while back
Yes, I will rename things back, I had some of this implementation work from some previous project based on an earlier version of this crate that I unearthed and adapted for qcell. :)
Yes, they will see
I think right now there are only 2 types per type of cell that get exposed. The |
It's unfortunate that giving the code a major overhaul like this means going through the whole thing once again with a fine-toothed comb looking for subtle bugs and issues. I have another crate I was hoping to get finished before the new year. I'll have to balance my time. I didn't want a It occurred to me that if your objection to the original code was the code duplication, perhaps that could be worked around with a macro to generate the common code. This could use the same Proxy/Marker approach you've introduced, but would generate the code in place, so wouldn't expose any new types. This would end up neater for the user. However, alternatively maybe we could "pivot" the whole concept of the crate and embrace the ValueCell as the main type (maybe call it something else), but that's an even bigger change, considering the doc rewrites and so on. Even so, the generic approach does remain more verbose, and potentially confusing to the user where there are type aliases. I will respond more fully to the other points in the morning. |
I currently implemented a 48-bit ID. I can roll that back quite easily, but I think this is a good default for a general use case. I am thinking beyond just a single project, what if we have multiple crates using this. They shouldn't run out of IDs just because we have a lot of crates. With 48-bit IDs that is extremely unlikely to happen, but it is possible with 32-bit IDs. Your memory argument rings hollow for me. If you really care about the difference of a handful of bytes, then you really shouldn't be using Most importantly, I don't like advertising Also, your comment about short-lived owners,
This is a perfect use-case for
This implementation that I am proposing is not theoretical either. It is heavily inspired by
I don't think that this is a large concern, as the very first generic parameter will tell exactly what type they are dealing with.
Macros tend to be harder to audit than types, so I'm a but wary of making anything but the most simple
Note, we can hide all marker marker types from the user except for Another beneift to this approach, if users want to build their own version of say, This could be encouraged by way of a proc-macro, allowing people to do this in safe code: #[derive(ValueCellOwner)]
#[vco="runtime"]
struct RuntimeOwner(u8); That way they can cater specifically to their needs. In the future, we can make |
Thinking about this some more, another possibility is to make |
Right, I went offline for a bit to try some different things and to think. There are still a number of problems: 48-bits is the same as 64-bit because of alignment and packing issues. So there's only really a choice between It's not true that someone using Regarding leaking internal types, the question is "How to teach this?". The competition is RefCell which is really straightforward. QCell at present is almost as straightforward, but if we're using type aliases then types are going to leak out, and we have to teach the user that if they see this weird internal type, then really it means QCell. For users of the basic 4 types, all the generic stuff is internal implementation details that only complicate things for them. Okay, if they want to make a QCell with a different size, then it is useful. But just for that! Nothing else. It shouldn't be exposed in the public interface for the basic types. I don't see how you are hoping to hide the types. Yes, you could make the 4 cell types be wrappers around the Value* types, but that is going to be even more verbose than the current implementation. What I like from your PR is the unification through the Proxy/Marker types. That I may end up borrowing from you at some point, if I can think of a way to do it cleanly. I wondered whether the unification would help in my current project, where I do need to switch between QCell/TCell/TLCell according to features. However actually it didn't help at all. All I require in that case is that the API is the same, or close enough. Interestingly I wanted to also offer a fully-safe option using RefCell (in case anyone doesn't trust qcell), but RefCell can't be persuaded to work through a compatible API, because it doesn't return references directly, just Ref or RefMut temporary objects. You code incredibly quickly and obviously all this higher type stuff and higher macro stuff is obvious and easy for you. However, if I put some of that code in front of most people in the Rust community they would run in fear. I work as a software engineer and I have to write clear code and ship solid products. At little bit of code duplication is manageable, as in this crate. I have to worry about how people who do not understand Rust that well will cope with whatever error messages come up as they try to get things working. I have to keep the conceptual burden and complexity level down, or else I will end up having to solve the problem for them. I also have to be concerned about company in-house unsafe reviewers, who probably are not genius-level. Complex approaches have to really bring a large payoff to be worth it. So, I think the downsides of the approach in this PR are still outweighing the advantages. |
Nope, it is stored as
Ok, I didn't think that through too much. But I think the other method of making it easy to create your own version of
I'm not so sure about this, given that these sorts of type tricks are the same as the ones in
That's understandable, I know I have a tendency to over complicate things, so if this is going too far that's fine. Error messages with generic types are a mess right now, and it would be nice if Rust used type aliases in error messages where they are used in source to make this manageable.
I think the thing that differs the most between us is the focus on error messages. If you value readable error messages over reducing code duplication then I think you should stick to the current framework. But I still strongly suggest that you at least switch to using 48-bit IDs for Thanks for your feedback! |
Would making This won't affect the error messages in a meaningful way, but it will make it easier to isolate each libraries usage of |
Whilst I like the unification in this PR, but I don't like the way it leaks out into the user experience and public API. Let's leave things as they are at the moment. With experience perhaps some other reason to adopt this approach will become clear and I'll come back to it. Thanks. |
Ok that makes sense. In that case can I make a PR to make the ID pluggable. That way different libraries can use their own IDs if they want. You can keep the default to 32-bit with ID reuse, but this allows it others to craft their owns IDs. This would also make it slightly easier to implement |
I was just thinking about how to restructure
qcell
after #9, and I came up with a way to generalizeQCell
,TCell
,TLCell
, andLCell
into a single generic type!The core interface would be:
All of the current types could be modeled like so,
The text was updated successfully, but these errors were encountered: