Skip to content

Revise type ascription operator to use type equality, not coercion #1539

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

Closed

Conversation

nikomatsakis
Copy link
Contributor

Revise type ascription operator to use type equality instead of coercing. This is the semantics that wound up being implemented and, on reflection, seems to represent a simpler and perhaps better choice.

I sort of like the idea of being able to write out what type an expression has without also triggering coercions. And most of the implicit coercions we have have explicit forms (e.g., &foo[..], for the example in the RFC, foo as &Trait for trait objects), so the : operator is not strictly needed there (though I am reluctant to encourage the use of as). (In any case, for the trait case, we may be able to improve inference in the compiler as well.)

I'm curious to get people's thoughts on this! Haven't fully made up my mind myself, though obviously I'm sympathetic since I took the trouble to write up the amendment. :)

cc @eddyb @petrochenkov @rust-lang/lang

coercing. This is the semantics that wound up being implemented and, on
reflection, seems to represent a simpler and perhaps better choice.
@eddyb
Copy link
Member

eddyb commented Mar 11, 2016

I wanted to use, e.g. 0: u32 to replace 0_u32 (which doesn't need coercion anyway), but that ship has sailed, and I agree with the slicing syntax point.

I have grown to like as personally, as it "just works", so I don't have any opinion on the matter.

@petrochenkov
Copy link
Contributor

I'd prefer as to be used only for dangerous/unchecked/lossy conversions and benign conversions to be performed by other means, including type ascription, so I'd be against this restriction.
Also, I feel the need in type ascription in its current restricted form so rarely, that I'm not even sure if it pulls its weight in the language.
(I also like the symmetry let a: Type = b; <=> let a = b: Type; for aesthetic reasons :)


Off-topic:

@eddyb: I wanted to use, e.g. 0: u32 to replace 0_u32

(0: u32).method() looks much worse than 0u32.method() IMO. I mentioned parsing of type ascrition as one of its main current drawbacks in the tracking issue, but no one seems to care enough.

@nikomatsakis
Copy link
Contributor Author

@petrochenkov

(I also like the symmetry let a: Type = b; <=> let a = b: Type; for aesthetic reasons :)

True, though there is a kind of symmetry -- in both cases, the : is telling you what the type is. That is, the two statements are not equivalent, but the role of the : is equivalent in both.

@glaebhoerl
Copy link
Contributor

I was quite persuaded by the original RFC's logic that type ascription should behave analogously to the existing positions in the language where we can specify a desired type, like function arguments, returns, and let bindings, which do provoke coercions.

@ticki
Copy link
Contributor

ticki commented Mar 13, 2016

I agree with @glaebhoerl, the very idea of type ascription is the ability to assert that a value having a given type, hinting the compiler. Without this, type ascription has very few usecases.

@glaebhoerl
Copy link
Contributor

With respect to the issues with lvalues vs. rvalues --- I'm not confident I fully understand the details, but wouldn't it be possible to resolve it such that a type-ascribed expression is demoted from an lvalue to an rvalue if and only if a coercion is actually triggerred? It makes intuitive sense to me that a coercion is a bit of runtime computation implicitly inserted by the compiler, which results in a temporary. (I.e., if I had written out by hand what the compiler inserted for me, it would have similarly created a temporary.) And it seems like this would rule out the problematic case which has to do with coercions happening in lvalue contexts. Or does this end up not flying because of things like deref coercions (which seem rather lvalue-y)?

@nrc
Copy link
Member

nrc commented Mar 13, 2016

Coercion was really the main motivation for having type ascription for me - I feel like asserting that a value has a type (without coercion) is not valuable enough to warrant it's own syntax (it could be a magic macro or something).

I think it is nice to be able to coerce with ascription, especially around DST type, e.g., where A and B are concrete types implementing T, to write &A: &T to make a trait object is useful. This is particularly useful in some nested cases. I don't quite remember the examples, but something like:

if ... {
    Box::new(A)
} else {
     Box::new(B)
}

doesn't type check, today you need to introduce two local variables just to make the coercion: let a: Box<T> = Box::new(A); a, which is pretty gross. With coercing ascription, you just add the : Box<T> ascription.

There are also problems with macros, e.g. you have to use assert rather than assert_eq because there is no way to force the coercion. This is nicely solved with ascription.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Mar 13, 2016
@nikomatsakis
Copy link
Contributor Author

On Sun, Mar 13, 2016 at 04:02:25PM -0700, Nick Cameron wrote:

Coercion was really the main motivation for having type ascription for me - I feel like asserting that a value has a type (without coercion) is not valuable enough to warrant it's own syntax (it could be a magic macro or something).

Well, I'm not sure I agree. To me, the main use case for type
ascription has always been annotating the return value of generic
functions, notably collect(): Vec<_>. (Many of the others are no
longer so important, since one can write e.g. MyType::default()
instead of Default::default(): MyType.)

I think it is nice to be able to coerce with ascription, especially around DST type, e.g., where A and B are concrete types implementing T, to write &A: &T to make a trait object is useful. This is particularly useful in some nested cases. I don't quite remember the examples, but something like:

if ... {
    Box::new(A)
} else {
     Box::new(B)
}

doesn't type check, today you need to introduce two local variables just to make the coercion: let a: Box<T> = Box::new(A); a, which is pretty gross. With coercing ascription, you just add the : Box<T> ascription.

Today one can write Box::new(A) as Box<Trait>, which is not so far
off from Box::new(A): Box<Trait>. But this is of course encouraging
the use of as, which we have to be careful with. It MAY be
possible to improve inference here, as well.

There are also problems with macros, e.g. you have to use assert rather than assert_eq because there is no way to force the coercion. This is nicely solved with ascription.

Hmm. I don't typically hit this, since usually it involves a coercion
to slices, and &foo[..] seems to work great in that case. I wonder
if it is also a case where we could improve inference.

@nikomatsakis
Copy link
Contributor Author

On Sun, Mar 13, 2016 at 05:17:57AM -0700, Gábor Lehel wrote:

I was quite persuaded by the original RFC's logic that type ascription should behave analogously to the existing positions in the language where we can specify a desired type, like function arguments, returns, and let bindings, which do provoke coercions.

Yeah, I think this is the strongest argument against the change.

@nikomatsakis
Copy link
Contributor Author

On Sun, Mar 13, 2016 at 01:15:53PM -0700, Gábor Lehel wrote:

With respect to the issues with lvalues vs. rvalues --- I'm not confident I fully understand the details, but wouldn't it be possible to resolve it such that a type-ascribed expression is demoted from an lvalue to an rvalue if and only if a coercion is actually triggerred?

It is possible but highly undesirable, in my opinion. Making
references can be confusing enough, but you don't want it to be
unknown whether you are making a reference into the original value or
into some (silently created) alias of the value. If we are going to
continue permitting coercions, then I think that original text (which
disallowed coercions in reference contexts) is the correct semantics.

Example:

let value: Box<Type> = Box::new(some_type);
let ref mut x = value: Box<Trait>;

Now x is pointing not at value but at some temporary. But if you
remove the coercion, it will be pointing directly at
value. Moreover, if you change the type of value to Box<Trait>, it
would ALSO be pointing directly at value.

UPDATE: s/impossible/possible/ in first sentence :)

@aturon
Copy link
Member

aturon commented Mar 14, 2016

@petrochenkov

I'd prefer as to be used only for dangerous/unchecked/lossy conversions and benign conversions to be performed by other means, including type ascription, so I'd be against this restriction.

It's definitely been the plan of record that as should ultimately be used for "dangerous" conversions (though of course it's a shame that such an innocent keyword is playing that role). And I agree that for type ascription to be useful in bringing about this state of affairs, it needs to coerce -- particularly for cases like coercing to a trait object.

I continue to wish that there were a simpler way of satisfying these needs than having two very similar mechanisms (as and :). cc @wycats

Also, I feel the need in type ascription in its current restricted form so rarely, that I'm not even sure if it pulls its weight in the language.

There's a subtext here I wanted to clarify: do you think the feature would pull its weight if it included coercions?

@petrochenkov
Copy link
Contributor

There's a subtext here I wanted to clarify: do you think the feature would pull its weight if it included coercions?

I don't know. Small improvements like coercions, better parsing, make the feature a bit more usable and tip the scales in favour of keeping it.

@bluss
Copy link
Member

bluss commented Mar 18, 2016

@nikomatsakis

Well, I'm not sure I agree. To me, the main use case for type ascription has always been annotating the return value of generic functions, notably collect(): Vec<_>. (Many of the others are no longer so important, since one can write e.g. MyType::default() instead of Default::default(): MyType.)

If we should follow the logical evolution of the language, maybe it's how we are calling collect that ought to change. For many other generic-interfaces I have come to prefer explicit types like you demonstrate: Cow::from(x), String::from(y) and so on. With FromIterator in scope we have the explicit and simple Vec::from_iter(....).

Just a thought from the perspective that adding a new operator just to solve .collect() or .into() ergonomics is maybe the wrong direction.

@nikomatsakis
Copy link
Contributor Author

I think I decided to just withdraw this RFC. In general, I'm of mixed minds about the ascription operator. @bluss makes an interesting point. Nonetheless, if we are to have it, I think I agree it should act analogously with let x: T = ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants