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

Type ascription (tracking issue for RFC 803) #23416

Open
nikomatsakis opened this Issue Mar 16, 2015 · 47 comments

Comments

Projects
None yet
@nikomatsakis
Contributor

nikomatsakis commented Mar 16, 2015

Tracking issue for rust-lang/rfcs#803. Important points:

  • Implement the expr:Type syntax
  • Ensure soundness with respect to ref positions:
    • let ref x = <expr> / let mut ref = <expr>
    • match <expr>: Type { ref x => ... }
    • (<expr>: Type).method_with_ref_self()
  • Permit coercions like &[1, 2, 3]: &[u8]

@alexcrichton alexcrichton added the T-lang label Aug 11, 2015

bors added a commit that referenced this issue Dec 19, 2015

Auto merge of #30184 - petrochenkov:ascr, r=nikomatsakis
This PR is a rebase of the original PR by @eddyb #21836 with some unrebasable parts manually reapplied, feature gate added + type equality restriction added as described below.

This implementation is partial because the type equality restriction is applied to all type ascription expressions and not only those in lvalue contexts. Thus, all difficulties with detection of these contexts and translation of coercions having effect in runtime are avoided.
So, you can't write things with coercions like `let slice = &[1, 2, 3]: &[u8];`. It obviously makes type ascription less useful than it should be, but it's still much more useful than not having type ascription at all.
In particular, things like `let v = something.iter().collect(): Vec<_>;` and `let u = t.into(): U;` work as expected and I'm pretty happy with these improvements alone.

Part of #23416
@petrochenkov

This comment has been minimized.

Contributor

petrochenkov commented Dec 22, 2015

There's one thing making type ascription less convenient than it could potentially be. While it has the same operator priority as as (it is modeled after as in general), i.e. the highest from binary operators, it's still lower than member access . (dot).

Compare these two expressions

let v = v.iter().collect::<Vec<_>>().sort(); // Parens are not required
let v = (v.iter().collect(): Vec<_>).sort(); // Parens are required
// Formatted on several lines
let v = (v.iter().
           collect(): Vec<_>). // Looks bad, man
           sort();

I'm not sure if it is worth fixing or if it can be fixed without hacks, but it is worth discussing at least.

@hoodie

This comment has been minimized.

Contributor

hoodie commented Dec 22, 2015

BTW: This has is a breaking change for crates that implement a similar macro syntax already, such as prettytables.

@jonas-schievink

This comment has been minimized.

Contributor

jonas-schievink commented Dec 22, 2015

@hoodie I opened #30531, since this seems worth tracking

@nrc

This comment has been minimized.

Member

nrc commented Jan 5, 2016

cc @nrc

@vi

This comment has been minimized.

Contributor

vi commented Mar 21, 2016

Is it a good idea to only do ascription inside parentheses, so let a = x : T is not ascription yet, but let a = (x : T) is? This can allow more room for some further syntax additions.

@petrochenkov

This comment has been minimized.

Contributor

petrochenkov commented May 6, 2016

For the record: a patch implementing support for method chaining with type ascription #33380

@ottworks

This comment has been minimized.

ottworks commented Jul 3, 2016

Why is this in the book if it won't compile? (1.9.0)

https://doc.rust-lang.org/book/closures.html

@hoodie

This comment has been minimized.

Contributor

hoodie commented Jul 3, 2016

@0TT what exactly?

@retep998

This comment has been minimized.

Member

retep998 commented Jul 3, 2016

@0TT Type ascription is specifically when adding a : T type annotation after an expression. Not in the parameters to a closure or a function, and not on a variable binding. Only when it follows an expression which I do not see any cases of on the page you linked.

@ottworks

This comment has been minimized.

ottworks commented Jul 3, 2016

Oops. I accidentally typo'd a semicolon as a colon:

let plus_two = |x| {
    let mut result: i32 = x;

    result += 1:
    result += 1;

    result
};
assert_eq!(4, plus_two(2));
@hoodie

This comment has been minimized.

Contributor

hoodie commented Jul 3, 2016

that happens, how about an RFC to replace all semicolons with a less confusable character?

let plus_two = |x| {
    let mut result: i32 = x✓

    result += 1✓
    result += 1✓

    result
}✓
assert_eq!(4, plus_two(2))✓

makes the language even look safer, everything is checked after all 😀

@ottworks

This comment has been minimized.

ottworks commented Jul 3, 2016

Should use emoji tbh. A language for the future.

@nrc

This comment has been minimized.

Member

nrc commented Aug 17, 2016

re the soundness question in the OP, this comment is probably the best summary.

@crumblingstatue

This comment has been minimized.

Contributor

crumblingstatue commented Dec 7, 2016

Before stabilizing this feature, I would like to see how this potentially impacts optional and named arguments, namely the expr : type syntax in function call argument position.

Could named arguments use this syntax if it wasn't for type ascription?

If yes, and no other syntaxes could be devised that are as optimal as name : type, I'd strongly recommend changing the syntax for type ascription, in favor of allowing the optimal syntax for named arguments.

I believe named arguments would be much more widely used than type ascription, and the syntax optimization for ergonomics should go there instead.

Ref rust-lang/rfcs#323

@aidanhs

This comment has been minimized.

Member

aidanhs commented Jul 18, 2017

@nrc do you have a link a discussion about the widening integers ergonomics initiative?

@glaebhoerl so &x as *const u64 as *const u8 -> cast (cast &x: *const u64): *const u8?

I'm not a fan of that I must admit. I see as and ascription as being very different - as feels like a nod at the fact that Rust is a systems language and can cheaply convert some 'primitive' types (functions, pointers, numbers) into other representations, even when the translation may be lossy.

Working with pointers in Rust is already not a great experience, it's very syntax heavy. I'd prefer not to go the same direction for integers.

@glaebhoerl

This comment has been minimized.

Contributor

glaebhoerl commented Jul 18, 2017

I see as and ascription as being very different

I completely agree, and I'm not sure what in my comment makes you think otherwise?

@aidanhs

This comment has been minimized.

Member

aidanhs commented Jul 18, 2017

I think those couple of sentences muddied my comment, please ignore them - the primary point was the extra syntax and I got sidetracked.

@glaebhoerl

This comment has been minimized.

Contributor

glaebhoerl commented Jul 18, 2017

Yeah, syntaxwise it's a legit tradeoff. I'm pretty sure there's also cases where the cast syntax would be more convenient (e.g. when it's actually sensible to leave off the target type).

@RalfJung

This comment has been minimized.

Member

RalfJung commented Jul 18, 2017

Lossy conversion without an explicitly stated target type sounds like a huge footgun to me.

TBH I don't see why as and : should be confusing. Converting things and saying that things have certain types are two very different operations. It doesn't seem to hard to explain this.

@omarabid

This comment has been minimized.

omarabid commented Aug 12, 2017

No progress on this one?

@scottmcm

This comment has been minimized.

Member

scottmcm commented Aug 12, 2017

👍 to deprecating as in favour of more targeted things, like explicitly-introduced left-to-right coercion points. Personally, I think all the non-coercion things that as does would be better as functions/methods.

@SoniEx2

This comment has been minimized.

SoniEx2 commented Mar 30, 2018

hey can we remove type ascription in favor of an std::identity function?

std::identity::<Type>(stuff)

current alternatives include:

  • (|x:ty|x)(expr) for use in macros.
  • ???

if we had it in std, we wouldn't need to introduce new syntax, and we wouldn't need to deal with the soundness issues and stuff, and it could be freely used in macros.

@SimonSapin

This comment has been minimized.

Contributor

SimonSapin commented Mar 30, 2018

Both a function or a closure cause a value to be moved, which affects its life time more than just a type annotation would.

@SoniEx2

This comment has been minimized.

SoniEx2 commented Mar 30, 2018

#[inline(always)] and the compiler should be able to detect identity functions.

@vi

This comment has been minimized.

Contributor

vi commented Mar 30, 2018

Or identity can be a magical intrinsic.

@sinkuu

This comment has been minimized.

Contributor

sinkuu commented Mar 31, 2018

The problem is that calling a function takes argument's ownerwhip. This cannot be cancelled by being an intrinsic (that is magical but stil has to appear to be a function), right?

let s = "foo".to_string();
(s : String).len();
println!("{}", s); // OK
identity::<String>(s).len();
println!("{}", s); // ERROR use of moved value: `s`
@SoniEx2

This comment has been minimized.

SoniEx2 commented Mar 31, 2018

It is meant for use without bindings.

If you wanna use it with bindings, just use let x: ty.

@sinkuu

This comment has been minimized.

Contributor

sinkuu commented Mar 31, 2018

Oh, of course. So it may be temporary lifetime things (for x: RefCell<_>, *x.borrow() : T works, but identity::<T>(*x.borrow()) doesn't work).

@SoniEx2

This comment has been minimized.

SoniEx2 commented Mar 31, 2018

Won't NLLs solve that?

@scottmcm

This comment has been minimized.

Member

scottmcm commented Mar 31, 2018

How about this as a more specific plan:

  • Move towards as becoming the syntax for type ascription (with coercion support)
    • Reusing as has the nice property that things like my_ref as *const _ don't need to change
  • Start linting to suggest methods instead of non-coercion as
    • error messages already do this, suggesting u64::from where infallible (not as u64)
  • Over time, add more methods to cover the rest of the cases
    • For example there's now NonNull::cast; raw pointers could get the same (or similar)
    • And maybe wrapping_from between integers, or some other variant from IRLO (thread) (thread)
  • Eventually (Rust 2021?) remove support for non-coercion as
    • Which is an allowed edition change as it's in the frontend and we'd be warning about any breaks
@Centril

This comment has been minimized.

Contributor

Centril commented May 26, 2018

I think @scottmcm's plan sounds good. Repeating what I said on #rust-lang: I can live with the problem of not having 100% consistency personally. Some inconsistency is to me better than not having the feature at all. Unfortunately, with the current language and our backwards compatibility promises, we can't achieve 100% consistency with typing judgements in the language across the board and I'm not advocating that we write:

let foo as type = expr;

That said, I think the idea due to @cramertj to have some sort of expr.type!(MyType) or say expr.as!(MyType) or even expr.at!(MyType) (read as: "expr is typed at MyType") is interesting. With postfix macros as in rust-lang/rfcs#2442 you could build that.

An example:

let vec = iter.map(..).filter(..).collect().at!(Vec<u32>);

The benefit of post macros here is that it allows chaining... however, I think it is unlikely to have many type ascriptions per expression.

zackmdavis added a commit to zackmdavis/rust that referenced this issue Jun 24, 2018

in which the trivial-casts word to the wise is tucked into a help note
The top level message shouldn't be too long; the
replaced-by-coercion/temporary-variable advice can live in a note. Also,
don't mention type ascription when it's not actually available as a real
thing. (The current state of discussion on the type ascription tracking
issue rust-lang#23416 makes one rather suspect it will never be a stable thing in
its current form, but that's not for us to adjudicate in this commit.)

zackmdavis added a commit to zackmdavis/rust that referenced this issue Jun 24, 2018

in which the trivial-casts word to the wise is tucked into a help note
The top level message shouldn't be too long; the
replaced-by-coercion/temporary-variable advice can live in a note. Also,
don't mention type ascription when it's not actually available as a real
thing. (The current state of discussion on the type ascription tracking
issue rust-lang#23416 makes one rather suspect it will never be a stable thing in
its current form, but that's not for us to adjudicate in this commit.)

zackmdavis added a commit to zackmdavis/rust that referenced this issue Jun 24, 2018

in which the trivial-casts word to the wise is tucked into a help note
The top level message shouldn't be too long; the
replaced-by-coercion/temporary-variable advice can live in a note. Also,
don't mention type ascription when it's not actually available as a real
thing. (The current state of discussion on the type ascription tracking
issue rust-lang#23416 makes one rather suspect it will never be a stable thing in
its current form, but that's not for us to adjudicate in this commit.)

While we're here, yank out the differentiating parts of the
numeric/other conditional and only have one codepath emitting the
diagnostic.
@Ichoran

This comment has been minimized.

Ichoran commented Jul 12, 2018

As a long-time user of Scala, I have only very rarely seen anyone confuse : Foo with .asInstanceOf[Foo]. Granted, as Foo is less different syntactically, but I still think it is very important for correctness to be able to distinguish between telling the compiler "I believe this type to be that, but check it for me" and "I want you to make this type be that if you can". That is, I want some way to avoid coercion, because coercion may be the wrong thing to do.

(Numeric types in Scala have a notion of "weak conformance" which means they can get coerced unexpectedly; this has turned into a a fruitful source of puzzlers. Rust has much less of an issue presently, but dropping the distinction between pure ascription and request for coercion is likely to erode the improvement. It would be especially pernicious if the very thing you would use to check your assumptions could break them instead!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment