Skip to content
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

Add a newtype keyword that creates a non-coercible, castable alias. #949

Closed
wants to merge 9 commits into from
Closed

Conversation

mahkoh
Copy link
Contributor

@mahkoh mahkoh commented Mar 6, 2015

Add a newtype keyword that creates a non-coercible, castable alias.
Rename type to alias.

Rendered

@mahkoh
Copy link
Contributor Author

mahkoh commented Mar 6, 2015

An interesting consequence of this definition is that the wrapping types, Wrapping<u8> etc., from the integer overflow RFC can be defined like this:

newtype w8 = u8;

and then the operations Add etc. can be defined on w8 as wrapping. The advantage of this is that casting between w8 and u8 via as works.

@mdinger
Copy link
Contributor

mdinger commented Mar 7, 2015

This is pretty minor but the keyword type is almost impossible to search for in documentation without a really smart search: searching for type on rustbyexample.com seems to narrow the results down to...everything. Searching alias is actually useful giving 4 total pages.

@CloudiDust
Copy link
Contributor

I believe this RFC is not actually proposing adding a new newtype keyword but repurposing type?

The problem with renaming type to alias, and then repurpose type is:

  1. People are accustomed to type creating aliases in Rust. (Though alias is indeed clearer, and some would prefer type to create new types. EDIT: The flip side is, some others would prefer type to mean alias, and neither is more objectively "correct". As long as we have a type keyword, there will be differing opinions, while newtype is unambiguous.)
  2. Should associated types be declared with the repurposed type or alias? Neither seems ideal.
trait Foo { type Bar = DefaultBar; } // repurposed `type` is supposed to create a newtype, but no newtype is created here.
trait Foo { alias Bar = DefaultBar; } // why are we creating "an alias" here? It's not clear `Bar` is an associated type.

So I think we should either:

  1. Keep the status quo, or,
  2. Simply add a newtype keyword to address the problems raised in this RFC, and otherwise keep the status quo. The fact that a separate newtype keyword exists is enough to tell people that make people suspect that type is not may not be for creating new types.

@CloudiDust
Copy link
Contributor

There is also another alternative that is:

Use newtype for newtypes, alias for aliases, and type for associated types.

But I don't think we need two new keywords and a large scale breaking change.


### Casting

Let `R1` be the binary, transitive, symmetric, irreflexive relation generated by
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand what relations are being generated here if they are not reflexive.

Copy link
Member

Choose a reason for hiding this comment

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

And I assume by "generated by newtype" you mean that if newtype T = U; then R1 contains (T, U)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let X be the set of types. Let A be set of tuples in X^2 such that (T, U) \in A iff newtype T = U or alias T = U or T != U are numeric types. R1 is the smallest transitive, symmetric superset of A in X^2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe a more intuitive definition is in order:

The newtype and alias keywords create an undirected graph such that the edge T <-> U exists if alias T = U or newtype T = U. To this graph we add the numeric casts u8 <-> f64 and so on.

(T, U) is in R1 if there is a path between T and U in this graph and T != U.

Copy link
Member

Choose a reason for hiding this comment

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

Does that mean that this is legal?

newtype Foo = Bar;
newtype Bar = f64;
newtype Baz = Qux;
newtype Qux = u8;

let x = 1 as Baz;
x as  Foo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, what you've just written is basically the cast c_uchar -> c_double which has to be legal.

Copy link
Member

Choose a reason for hiding this comment

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

You use consistently 'has' for a lot of things that are not obviously compulsory to me (and others); maybe you could expand?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The motivation of this RFC is to create a system that, in the case of the numeric typedefs in liblibc, is not harder to use than the current system. If casts between typedefs of different built-in numeric rust types are not allowed, then the RFC has failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If casts between types newtype T = U and built-in types W != U (both numeric) are not possible, then the system becomes completely useless because you can only cast between c_long and i32 on Windows and only between c_long and i64 on 64 bit unix.

@huonw
Copy link
Member

huonw commented Mar 7, 2015

cc #186

@bill-myers
Copy link

Yes, this is useful.

Another example is casting an &mut [f32] to &mut [F32OrderedWithNaNsAtEnd] so that it can be sorted.

I think it would be also nice to have a version that allows casting only from the newtype to the type, to represent types with added or removed constraints (the other direction would only be allowed for code that has "private" access to the newtype).

For example, F32NotNaN could be a newtype where F32NotNaN -> f32 conversion is allowed, but not viceversa.

This could perhaps be done by allowing these coercions for newtype structs.

@huonw
Copy link
Member

huonw commented Mar 8, 2015

Are there any places this is required in std (i.e. for 1.0 stable API)?


The same rules as for `Fn` apply to `FnOnce`.

#### Default impls
Copy link

Choose a reason for hiding this comment

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

This rule now covers Send and Sync (afaik), no need to mention them above.

Copy link
Member

Choose a reason for hiding this comment

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

This rule is also implied by the OIBIT rules, i.e. could be written as "Traits with default impls behave as if newtype T = U; is the same as struct T(U);."

@mahkoh
Copy link
Contributor Author

mahkoh commented Mar 8, 2015

I've uploaded a significant revision of the RFC that should hopefully address most of the points raised here.

Changelog:

  • Don't rename type for now
  • The rules for casting have been relaxed: Vec<c_ulong> as Vec<u64> is now possible
  • A new property has been added that governs when X<U> as X<T> is allowed.
  • The rationale section has been completely rewritten and expanded to explain the technicalities of the design.

These new rules also allow the cast mentioned by @bill-myers above:

Another example is casting an &mut [f32] to &mut [F32OrderedWithNaNsAtEnd] so that it can be sorted.

@mahkoh
Copy link
Contributor Author

mahkoh commented Mar 8, 2015

Please refrain from commenting in the now collapsed comment threads. Instead open a new one.

@huonw

Are there any places this is required in std (i.e. for 1.0 stable API)?

Some libc types are exposed in the public interface. Apart from that, the implementation makes heavy use of libc types.

@mahkoh
Copy link
Contributor Author

mahkoh commented Mar 8, 2015

I think I've come up with a somewhat simpler and more powerful way to make X<T> as X<U> casts safe. I'll try to include this tomorrow.

@mahkoh
Copy link
Contributor Author

mahkoh commented Mar 8, 2015

After thinking about it some more, the current version is as good as it gets for now.

@Havvy
Copy link
Contributor

Havvy commented Mar 11, 2015

Why not rename type to assoc instead? That a static association is just an alias seems like a special case.

@nikomatsakis
Copy link
Contributor

Thanks for the detailed RFC. No question that generalized newtype deriving could be useful, but it doesn't fit into the current priorities. Therefore we're going to postpone this RFC using the existing issue #261.

@drewm1980
Copy link

Shouldn't something like this be a 1.0 blocker? It seems like you don't want people rolling their own solutions to get around a pain point in something as fundamental as newtype.

For example, it would be nice to be able to do checked addition, max_value(), etc... on a newtyped int without a bunch of boilerplane:

http://is.gd/7Cdzkd

Credit: this came from help I got during an IRC discussion with flan3002.

Thanks!

@Havvy
Copy link
Contributor

Havvy commented Mar 28, 2015

Rust 1.0 is the point that backwards incompatible changes won't be allowed. It doesn't mean every wanted feature will be in it. This feature is wanted, but can be added without a compatibility hazard, so there's no reason to block 1.0. With a six week release cycle, it'll be in as soon as somebody prioritizes it.

@drewm1980
Copy link

@Havvy, I get that. It seemed to me like newtype is more of a fundamental part of how the typesystem works, and doesn't really count as a "feature." If it can be added later in backwards compatible way on top of the existing types (i.e. without resorting to another flavor of struct), that will be awesome and surprising to me, but it won't be the first time the rust devs have pulled off such a feat :)

@oli-obk
Copy link
Contributor

oli-obk commented Dec 11, 2017

reopened rfc in #2242

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants