-
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
Introduce a newtype keyword. #186
Conversation
I am splitting hairs but there is a semantic error in your examples: Multiplying two "inchy" numbers does not give you another inch but a square-inch. I like the overall idea but I think to evaluate to real usefulness there should be a better example.
An error that could still happen with your proposal is:
|
Ah dang you are of course right! Not sure what I was thinking. |
There. Hopefully it's a bit more clear. |
let dist = Inch(calc_distance(start_inch as int, end_inch as int)); | ||
``` | ||
|
||
It also looses type safety. |
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.
loses or loosens
This seems like it could be a macro that expands into the |
Exactly, just like bitflags that are created with a macro. This idea is useful for ergonomics only. The Is the constructor accessible from other modules? As in |
Could you go into more precise detail about what this means? What does "same capabilities" imply? What's happening under the hood? On what basis is the second, modified example code accepted? If it boils down to just the fact that, given
would be possible. This would require being more explicit about which "capabilities" the new type should inherit, but it's not clear that this would entirely be a drawback. |
Hmm I had not considered using a macro for this. If it works then that could be preferable. I am a bit curious on how If we only would allow
Following the scoping of other types,
That's a mistake, thanks for pointing it out. It should be initialized directly from the number literal.
Yes I need to that, thanks.
You are correct. Now this raises some interesting questions. Would we be allowed to define our own base
It's an interesting idea. As you say it might not necessarily be a drawback, in some cases it might also be an advantage. I don't know how one would implement the |
imagine if tuple structs (and plain tuples) could be made to delegate methods and fields to all their components (prioritised by position), then you could use them more effectively as new types, and as intersection-types .. it could be a superior replacement for some of the uses of struct-inheritance in c++. this would make it easy to refactor code between OOP and component styles, and refactor code whilst reducing dependancies in a system |
Add notes about exports.
Here is your code ported to the sort of macro I am thinking of: live on the playpen |
@pnkfelix That's cool, but it's more like |
@jfager Well, personally I think the set of "correct traits" is impossible to automatically infer ... e.g. like the comment above says, should you get multiplication for inches? It doesn't really make sense from the point-of-view of what they represent. So really I think one should be providing a list of the primitve operator traits to implement. I did not include that in my macro definition, but I think it is feasible to revise it to do so. It would probably be better to use a (Of course its possible that the above is not what the author is asking for -- that they really do want to get every trait, including those like multiplication that do not make sense to me.) |
The most valuable part of this proposal is the potential to add something akin to GeneralizedNewtypeDeriving from GHC. The current overhead in creating tons of impls for wrappers around common types discourages users from creating newtypes to enforce compile-time guarantees and instead encourages them to just go ahead and use the plain type. For instance, if, as a user, I have to write: struct Inch(uint);
impl Add<Inch, Inch> {
// etc.
} and write boilerplate impls for every single trait I want from the underlying type, I might just not use them. If I could instead do: #![feature(generalized_newtype_deriving)] // or whatever
#[deriving(Add, Sub, etc.)] // Compiler error if uint does not impl any of these traits and they can't be derived normally.
newtype Inch(uint); I would be significantly more likely to use newtypes to make compile time guarantees. Boilerplate trait impls are one of rust's ergonomic weak points in my opinion, and features like this go a long way to reduce the amount of boilerplate one has to write. |
So the "newtype" inherits all of the base type's fields and methods, yet is a more specialized form of the parent. I assume it can also be passed to a function taking the parent type. This is basically limited struct inheritance, right? The only limitation is that the derived type is prevented from adding new fields. |
Thanks @pnkfelix for the macro implementation. Unfortunately it doesn't seem to handle arbitrary traits, and I don't know how we would accomplish that. As it seems now, the better approach would indeed be like what @reem and @glaebhoerl suggests. Would it be possible to allow us to derive from arbitrary types and traits?
Also could this be applicable for all tuple structs?
And how to handle the methods of a struct? Should we be allowed explicitly derive them or might it be implicit?
The idea was not struct inheritance as it would not be possible for a |
And I also agree with @pnkfelix and @laszlokorte that the current proposal isn't semantically sound, as multiplication doesn't make sense for |
Just based on what GHC's GNTD does:
Yes.
What would the "base" type be in this example? The tuple type
No. In this case you would have to explicitly wrap/unwrap the newtype. (Again, if we were to do the same things GHC does, which has been proven to work well. We would have to think much harder about doing them differently.) |
Mention GNTD as a useful, possibly preferred, alternative.
That's cool.
The written out type should be simply
That's fine. It could still be a way for code reuse:
This way we could also choose to implement |
Yes, after changes to
Yes. Similarly, all fields are cloned separately in a struct that derives Clone.
I don't think so. We couldn't derive the methods of |
The "base" type is the type which the newtype is a newtype "of" (e.g., in your proposal,
and
And given:
what GNTD does is simply transmute the (For the record, |
From an implementation perspective, the current macro-based Specifically, there's no way to work out what methods/signatures should be implemented for a generalised That is, GNTD would be a non-trivial extension to our current deriving infrastructure, moving it from being a plain macro to something with (relatively) deep compiler hooks. (As I was typing this, @glaebhoerl wrote a description of a possible implementation-via-compiler-magic.) |
I agree tuple struct + newtype deriving seems more orthogonal. One thing I've never understood why GHC didn't provide is: #[deriving_all_but(...)]
struct NewType(Type) Might as well have both, but IMO this variant is the more useful one. |
👍 This is a much-needed improvement—AIUI Go even went so far as to make all type declarations create new types. Can newtypes be casted to their base types? For example: newtype Metre = uint;
fn frobnicate(x: uint) -> uint { x * 2 + 14 - 3 * x * x }
println!("{}", frobnicate(x as uint)); I like the idea of adding something like Also, some of the semantics aren’t so well-defined. The best way to solve this IMO is to allow |
+1 The fact that there exists a well known hack for implementing this already shows there is a need and makes me want a real solution so we can avoid the hack. Should explicit coercions (using |
There should be explicit, but certainly not implicit, coercions. Since the newtype is always the same in memory as the type it is wrapping, I see no reason not to allow explicit casting - all it will do is remove many unnecessary destructurings. |
I think the right "real solution" would be either GeneralizedNewtypeDeriving or module-scoped existentials a la ML. Do you have a reason to think otherwise? |
They seem more complex solutions - why bother with the extra wrapping entailed by using a struct rather than a newtype? module scoped existentials seem strictly more complex and I don't see the motivation for the extra complexity. |
Sorry, I think I might've accidentally been conflating aspects of this discussion with another similar, recent one - to clarify, what were you referring to as the "well known hack" already in use? |
Thanks @glaebhoerl for the explanation of GNTD.
Yes I don't see any reason why not.
Syntactically a But the big advantage with GND is to exclude (or include) selected traits. When for example Could we combine the newtype with GND? By default we derive everything, but we can explicitly specify what to derive?
And what would it take to automatically discover all visible traits for a type? |
@P1start
Good points. |
6357402
to
e0acdf4
Compare
I would like to discuss this RFC at the weekly meeting this week (Tuesday). I will propose we close it and tag the RFC as postponed. I strongly believe we should have newtypes and generalised newtype deriving in Rust, however, I believe the work and discussion on the design should be postponed until after 1.0. In order to ensure we can implement this later, I would like to reserve the If anyone has a strong argument for why this should be done before 1.0, or has an opinion on naming, please let me know. |
Discussed at the weekly meeting today (https://github.com/rust-lang/meeting-minutes/blob/master/weekly-meetings/2014-09-23.md). This is definitely something we will consider in the future, but not before 1.0. We decided not to reserve a keyword - no one really liked |
…nique-history-location-state RFC: Track unique history location state
Pretty