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

Make the turbofish syntax redundant #2544

Closed
wants to merge 16 commits into from

Conversation

varkor
Copy link
Member

@varkor varkor commented Sep 17, 2018

Make disambiguating generic arguments in expressions with :: optional, allowing generic arguments to be specified without ::. This makes the "turbofish" notation no longer necessary.

Rendered

This makes the following valid syntax:

struct Nooper<T>(T);

impl<T> Nooper<T> {
    fn noop<U>(&self, _: U) {}
}

fn id<T>(t: T) -> T {
    t
}

fn main() {
    id<u32>(0u32); // ok
    let n = Nooper<&str>(":)"); // ok
    n.noop<()>(()); // ok
}

The syntax ambiguities (a<b,c>(d)) and a<b>>c are resolved in favour of generic expressions.


This is an updated and more considered version of the RFC put forward a little while ago.
Thanks to @Centril, @comex, @joshtriplett, @kennytm, @petrochenkov, @rpjohnst, @scottmcm, @ubsan and @xfix for their feedback (and everyone else who chipped in)!

cc @aturon, @eddyb, @withoutboats

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Sep 17, 2018
@SimonSapin
Copy link
Contributor

However unusual a corner case, I feel very uncomfortable with making breaking parser changes for existing code.

On the other hand this change is a perfect candidate for edition switches since it can be entirely crate-local. I’d much prefer it only applied in Rust 2018 or, if it’s too late, in the following edition. The turbofish has been with us since 2011, before Rust 0.1. We can live with it a few more years.

@Centril
Copy link
Contributor

Centril commented Sep 17, 2018

However unusual a corner case, I feel very uncomfortable with making breaking parser changes for existing code.

There's precedent for doing this with rust-lang/rust#53854.

I’d much prefer it only applied in Rust 2018 [...]

Nominating for the next meeting to discuss this possibility.

@SimonSapin
Copy link
Contributor

I have not tried reading that diff, but the title of that PR says “edition changes”. So… is that an agreement with my comment?

@Centril
Copy link
Contributor

Centril commented Sep 17, 2018

@SimonSapin no; the precedent was for applying the grammar changes in Rust 2015.

@SimonSapin
Copy link
Contributor

I was not involved in that discussion but my opinion is the same. I think this is a bad precedent.


An initial implementation is present in https://github.com/rust-lang/rust/pull/53578, upon which the
implementation may be based. The parser will now attempt to parse generic argument lists without
`::`, falling back on attempting to parse a comparison if that fails.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not a language argument, but you are underestimating the work required to do this correctly.
On encountering < parser needs to enter some kind of new speculative mode suppressing all side effects, including non-fatal diagnostics.
We can ignore these details if the rollback is done only as a part of best-effort error recovery, but not if it's a part of the language, so the feature has some global effect on parser implementation rather than just calling self.clone() in one place.

Copy link
Contributor

@petrochenkov petrochenkov Sep 17, 2018

Choose a reason for hiding this comment

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

Additionally, parser can accept pretty strange things and then report "semantic" errors later, but we won't be aware of these errors when disambiguating with backtracking.
This problem already exists in the formulation "what exactly is accepted under cfg(false)?", but in that case it's probably easier to fix e.g. by doing AST validation during expansion.

Copy link
Member Author

Choose a reason for hiding this comment

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

I noticed this was necessary with rust-lang/rust@307ea60, though I imagined this wasn't sufficient for a final implementation. The language in the RFC wasn't intended to insinuate how difficult the task would be: the proof of concept is definitely only that!

@dherman
Copy link

dherman commented Sep 17, 2018

However unusual a corner case, I feel very uncomfortable with making breaking parser changes for existing code.

I thought it might be helpful to weigh in with my experience at TC39, which spends a lot of effort on retaining compatibility but nevertheless does sometimes make technically breaking changes. It's true that the bar is high for breaking changes, which is why it's good to get empirical evidence about real-world breakage. But at the end of the day, we've found with JavaScript that -- if we are cautious and use good judgment -- there is some budget for de jure incompatible changes that are de facto compatible. To me, the fact that even something as high-usage and conservative about backwards compatibility as JavaScript is able to make changes like this suggests that Rust can as well.

My feeling is that if the language and ecosystem are better off for it, and it doesn't cause churn in practice, Rust should be willing to make formally breaking changes like this. (And given my personal experience both with teaching Rust to newcomers at work and designing APIs to try to avoid turbofish, I personally hope we do!)

@estebank
Copy link
Contributor

And given my personal experience both with teaching Rust to newcomers at work and designing APIs to try to avoid turbofish, I personally hope we do!

I would be interested in hearing (possibly off thread to avoid spamming) about what you've found the largest problems for newcomers when it comes to turbofish. I can think of a few:

  • discoverability
  • "weird" syntax
  • understanding of the semantics

Also, I am interested in hearing what the main reason(s) for avoiding the need of turbofish in your API surfaces are that would be rendered moot by changing the syntax.

I am personally of the inclination that rustc should actually accept a superset of valid Rust code for more specific diagnostic errors, which would make merging @varkor's change almost as-is to inform the suggestions emitted something I would enthusiastically push for as it would help with the three items listed above, while changing the parsing semantics away from a regular syntax by accepting now-incorrect code a bit worry-some as it would put a higher burden on third-party implementers. (I understand the low likelihood of anyone actually trying to make an independent Rust compiler that matches nightly or even current stable rustc, but still.)

@SimonSapin
Copy link
Contributor

@dherman Fair points, but I think that the equation is significantly affected by Rust having this edition mechanism that JavaScript doesn’t.

We’re doing opt-in switches already, let’s use them for what they’re for.

@rpjohnst
Copy link

rpjohnst commented Sep 17, 2018

On the question of teaching, we also ought to consider the affect of type ascription. If we had type ascription but not this, we could teach and use the language entirely without turbofish, without worrying about compatibility or edition switches or weird syntax or backtracking.

@pcwalton
Copy link
Contributor

pcwalton commented Jan 12, 2019

@graydon The fact that this is spiraling into a discussion about governance is disconcerting to me. To be frank, it's hard to read this as anything other than a wish to return to the days of you as BDFL. Rust just does not have that model anymore, and hasn't for years.

@H2CO3
Copy link

H2CO3 commented Jan 12, 2019

But the turbofish is itself a hack.

I get your point, and then we just disagree here. The reason why I don't see the turbofish as a hack is that it's a very simple way of completely eliminating a problem that has been present (and a pain point) in older languages. Given the circumstances, I would even call it elegant compared to other solutions. (And now I'm expecting the stones to be thrown.)

So it's a question of which hack is better.

So, if we regard the turbofish as a hack, then it's the smaller one, because it's local, ie. it doesn't have any other side effect outside its immediate scope.

@joshtriplett
Copy link
Member

joshtriplett commented Jan 12, 2019

it's clear evidence to me that the chorus of "please stop changing things" input from the community is being flatly ignored by the language team.

Speaking as a member of the language team: no, it isn't. We're actively discussing such things, trying to find new processes, and in any case saying no quite often. Half the things you commented on in your commenting spree were those we had already decided against, or had not yet decided on and were leaning against. That they haven't yet been closed is not an indication that they're moving forward.

Yes, some improvement is needed. In particular, there was a spree of "ergonomics" RFCs that (in my opinion) didn't use the right criteria for evaluating the overall impact on the language. But I feel that that has slowed down, and we're cleaning up the result and trying to make sure the default answer is "no" without strong rationale.

That said, this RFC hardly seems like an example of "we're making the language more complicated"; on the contrary, this change makes it simpler and more consistent.

@rpjohnst
Copy link

rpjohnst commented Jan 12, 2019

There are real benchmarks provided in this thread.

My comment wasn't about the speed of parsing, so those benchmarks are mostly unrelated. It was about what we did after the implementation-related slowness- which Rust also has, and thus might also need to work around in similar ways.

That said, this RFC hardly seems like an example of "we're making the language more complicated"; on the contrary, this change makes it simpler and more consistent.

That's the main point of contention here. I personally think the opposite.

@pcwalton
Copy link
Contributor

I think it's obviously quite unlikely that this PR is going to be merged, and that's a success of the governance model, not a failure. There was a FCP, the community weighed in with "wait, we don't like this" after it went around on Twitter, and so now it's not going to happen. What's the problem?

@joshtriplett
Copy link
Member

@pcwalton

I think it's obviously quite unlikely that this PR is going to be merged

I'm curious where you're getting the "obviously" from.

@skade
Copy link
Contributor

skade commented Jan 12, 2019

@pcwalton I hate to say it, but, as a project member, I see @graydon's point. It's neither the first time he's voicing it, nor is he the only person who is raising it. There have been multiple calls for stability in the ongoing 2019 blog campaign and I do know that some people approach Graydon with things that they don't feel that the current project staffing could deal with.

There's a growing feeling that I notice on multiple occasions that people don't believe their input matters, and large resources being spent on multiple tries to remove the turbofish are an instance of that problem. I want to add that this is not necessary my opinion, but that doesn't make the feeling go away.

I would hope those issues would be pointed to the community team more, but what I can definitely say is the community team is also internally not consulted on things like this, though assessment of situations like this is definitely our expertise.

I know it's frustrating that generally small things spark these kinds of discussions, but here we are.

@joshtriplett
Copy link
Member

@skade

There have been multiple calls for stability in the ongoing 2019 blog campaign

I've read many of those blog posts, and I agree. (Some of them are more constructive than others, but the sentiment is clear.) I feel the same way myself.

@graydon
Copy link

graydon commented Jan 12, 2019

a wish to return to the days of you as BDFL

@pcwalton First: I never asked to be and never was (and strenuously resisted) any title like that when I was "de facto tech lead" inside a team at moz. I regularly lost arguments to you, Niko, Dave, Marijn, Brian and numerous community members, managers and even interns. You're misrepresenting my tenure by using a term like that.

Second: those were some of the hardest and worst days of my life and I wouldn't take the job of even de facto tech lead of this project again if you paid me a fortune for it. If you think I have any nostalgia for that time, you are profoundly misunderstanding me.

I'm disappointed and hurt by this statement, and surprised that you'd even say it, given the circumstances of my departure.

@Manishearth
Copy link
Member

@graydon

I agree, and I don't want to be making pointless arguments in bad faith or anything

to be clear, I wasn't saying that was the case with your comments, it was more of a general "we are heading in this direction and it's good to be aware of it so we save ourselves hours of arguing" 😄

My comment wasn't meant to be a rebuke of anyone, more of just a way to take stock of the state of the discussion.

but it nevertheless embodies a sentiment that is clearly felt by a large contingent of the community

I think there's a good way and a bad way to express the underlying sentiment, I'm not passing judgement on that sentiment itself in the tweet you linked, just the way people typically express that sentiment.

I do think that discussing this and other general governance issues are off topic for this thread, though.

The part of the governance discussion that's relevant to this RFC: it's clear to me that the same governance issues that plagued us during the run up to the edition are rearing their heads again. Given that we no longer have an edition deadline, we don't need to postpone thinking about them so that we can ship things. There have been multiple strong calls for solving "governance debt"/"process debt"/"organizational debt" this year, and it seems likely to me that we're going to decide to focus on that this year. Perhaps we should wait on resolving those first? Default to postponing contentious RFCs that stretch the limits of governance, or something like that.

@graydon
Copy link

graydon commented Jan 12, 2019

Speaking as a member of the language team: no, it isn't. We're actively discussing such things, trying to find new processes, and in any case saying no quite often

@joshtriplett I'm heartened to hear it, and apologize for the bull-in-china-shop nature of my comments here. This is the last thing I want to be intruding on and I wish I could stay away for good. I'm feeling a bit too emotional about the idea that I'd be wanting to be the governance of the project -- it basically destroyed my life last time I was involved to that extent -- and I hope the divergence I perceive is somehow resolvable. I am not calling for a coup or anything, just some indication of priority being given on acknowledging and responding to the evident gap in opinion. I wish you & the governance system of the project all the luck in resolving the tension here. There have in the past been divisions deep enough lead to people advocating forks; I would love for that to never happen to Rust, but it's only avoidable through conscious compromise, giving up on some of one's wants.

@joshtriplett
Copy link
Member

joshtriplett commented Jan 12, 2019

@graydon For the record, I do believe you're perceiving a real problem, but it's one that the language team (and other teams) has seen and is dealing with as well.

Speaking specifically for myself at the moment, this is something I deeply care about, and more often than not I find myself the voice of dissent in the direction of "no". :)

@graydon
Copy link

graydon commented Jan 12, 2019

My comment wasn't meant to be a rebuke of anyone, more of just a way to take stock of the state of the discussion.

@Manishearth I did not read it as such. It seemed an honest and wise assessment. My comment about speaking in bad faith was to indicate that I recognize the risk of speaking that way myself, and that I wish to refrain from doing so.

I do think that discussing this and other general governance issues are off topic for this thread, though

Clear enough. I ought to step away anyways, it's not doing myself or anyone here any further good. Apologies.

@pcwalton
Copy link
Contributor

@graydon I'm sorry. I apologize, that was out of line on my part.

(I had an entire post here typed up, but I think it would probably do more harm than good to litigate this further, especially in this RFC.)

@anp
Copy link
Member

anp commented Jan 12, 2019

Forgive me for piling on -- I'd also like to quickly apologize for setting a poor tone when I brought this up on Twitter. I could have easily separated out my need to vent from my desire to have a conversation about precedents, and I should have.

Re: this RFC, from @Manishearth:

The part of the governance discussion that's relevant to this RFC: it's clear to me that the same governance issues that plagued us during the run up to the edition are rearing their heads again. Given that we no longer have an edition deadline, we don't need to postpone thinking about them so that we can ship things. There have been multiple strong calls for solving "governance debt"/"process debt"/"organizational debt" this year, and it seems likely to me that we're going to decide to focus on that this year. Perhaps we should wait on resolving those first? Default to postponing contentious RFCs that stretch the limits of governance, or something like that.

The point has been made several times about apparent themes of 2019 posts, specifically how many community members have called for adopting a more conservative stance towards changes to the language. I've waffled quite a bit myself over time, but something has started to happen that's changed my mind a little bit.

Rust is kinda doing really well? Where a necessarily short-term view sees scaling issues, I'm starting to see a really rosy longer-term future as long as we keep figuring the short term stuff out in good faith. If your metric for success is to grow the community and help the world deliver safer, faster software with less work, "scaling problems" translates roughly to "problems due to unexpected levels of success." In no small part thanks to the work of everyone in this thread. It's a sucky side effect of success, but it's still pretty awesome to have it, IMO.

Perhaps this is too generalized, but there's a fun cycle here:

  • more people depend on the language (woo)
  • the community grows (woo)
  • the perceived value of the project increases (woo)
  • the increased importance of getting each change right grows too (hm)
  • the emotional value of "real estate" (RFCs, features, etc) grows too (hmm)
  • the speed of making significant changes slows
  • people perceive the language to be more stable and predictable
  • more organizations depend on the language (oh no, but also woo)

Not sure how to resolve this, but if there's a chance that everyone here has done such important work that Rust is still valuable to the world in 10/20/30?/40??/50??? years, it's worth taking time to get answers to important questions right.

@sayrer
Copy link

sayrer commented Jan 12, 2019

One alternative I didn't see in the RFC:

Require space around "<" and ">" for comparisons, and prohibit leading and trailing space in generics.

Maybe this is an obvious non-starter, but I thought I would mention it.

@estebank
Copy link
Contributor

@sayrer right now I can only think of once place in the syntax where whitespace is significant, and thats the nightly-only emplacement syntax, and that is because <- is a valid token, so if x<-1 {} must be written as if x < -1 {}. There's no technical reason it can't be implemented that way, but making whitespace significant can be a similar wart/piece of syntax context that developers will need to keep in mind when writing their code, not dissimilar to the turbofish, although granted, with a lower likelihood of being hit due to the predominant style when writing types, but just as likely to be hit by accident when writing comparisons as emplacement is.

@Ekleog
Copy link

Ekleog commented Jan 12, 2019

Prohibiting leading and trailing spaces in generics would mean one cannot have a closing > alone on its line, which is currently AFAIR the advised coding style for generics-heavy code.

@sayrer
Copy link

sayrer commented Jan 12, 2019

@estebank I agree it's a bit of a departure for Rust, but I think rustfmt already does this. One useful pattern for making these changes might be to lift things from rustfmt into the language itself, at the right time. Also, note that the nightly-only emplacement syntax example is a fairly similar issue.

@Ekleog Hmm. I guess only leading space really needs to be prohibited. This seems pretty easy to understand, as it isn't too different from HTML, etc.

@Centril
Copy link
Contributor

Centril commented Jan 13, 2019

the nightly-only emplacement syntax,

@estebank this was removed from nightly, #2387

EDIT (per @estebank's notes): the feature is no longer active, even in nightly, but the token is still around (which causes this to still be relevant) because of the fallout from rust-lang/rust#48333 [on the wider ecosystem]rust-lang/rust#50832.

@withoutboats
Copy link
Contributor

withoutboats commented Jan 13, 2019

I think the turbofish is in itself a net negative for Rust - when I learned Rust, the error message did not helpfully suggest turbofish when you tried to use normal braces, so I spent some time unaware that I could specify the type as needed without pulling this expression out into its own let binding. Now, the compiler at least tells you what to do, but its still a stumble and it would be ideal for it to not to be necessary. But I'm wary of complicating the grammar and implementation, and I was optimistic about seeing good, nuanced and precise information about what the costs of removing the turbofish are.

I've gotten the sense this position is pretty roughly the lang team's previous consensus last time we talked about it. In other words, the turbofish is a cost we know, and we'd like to get an accurate weight of the cost we'd be taking on in exchange for it.

Unfortunately, that discussion is not what's emerged on this thread. This thread has become, like so many RFCs these days, full of sound and fury. Exercises like this are unproductive, exhausting, and emotionally draining for everyone involved. This much content, and with this much emotional fervor behind it, overwhelms the projects' processes and grinds other areas to a halt while we all deal with the repercussions of a discussion that erupts in the manner that this has.

I'm not sure what to do about this in the long term, but in an immediate sense I'm calling a cool down on this thread. I'm locking discussion for a couple of days or so and when it gets unlocked I hope it will be conducted with a decorum appropriate to a professional open source project.

@rust-lang rust-lang locked as too heated and limited conversation to collaborators Jan 13, 2019
@nrc
Copy link
Member

nrc commented Jan 13, 2019

I fear the discussion here is getting way beyond the scope of this RFC. But to add to it anyway: I think it is correct to be concerned about the rate of change, and I know that both the language and core teams think a lot about rate of change. However, I think avoiding change for the sake of avoiding change is as bad as change for the sake of change. Let's focus on the magnitude of the change, as well as the costs and benefits.

IMO, turbofish is a wart. It is not a bad wart, as @skade says, it is easily learnt and infrequently encountered. Nonetheless, it is still something which has to be learnt and is not intuitive. If we can get rid of it, then I see that as making the language less complex for the user. As such it is the kind of 'polishing' change that I would like to see, c.f., change which truly adds something to, or changes the language. I think there is a valid and interesting debate to be add about whether the LL(k) property is an important one to keep. To my mind it is not - it is not the kind of formal category which makes a huge difference (c.f., type soundness, for example) and I think complexity for readers and writers is far more important than complexity for implementers or theoreticians (which is not to say it is not important, just less so).

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jan 24, 2020

So a little over a year ago, @withoutboats wrote:

I'm not sure what to do about this in the long term, but in an immediate sense I'm calling a cool down on this thread. I'm locking discussion for a couple of days or so and when it gets unlocked I hope it will be conducted with a decorum appropriate to a professional open source project.

Obviously, it's been more than a few days! Over the course of several lang team meetings, we've been discussing what to do about this RFC, and I've also spoken with @varkor. The truth is that it's quite complicated. But one thing is clear: Whatever we do, it won't happen on this particular pull request. If nothing else, this thread history is far too large for any reasonable human to catch up on. Therefore, the only action I am taking for the moment is to close this RFC.

In terms of next steps:

  • Most of us on the lang team continues to feel that, all things considered, this would be a positive change for the language. However, we also recognize that there is a very sizable set of people who feel otherwise, and this alone is definitely reason to give pause and be triple sure.
  • If we were to reconsider this proposal, we would want to begin with a thorough review of the concerns raised here, to ensure that the they are not lost and that we have considered them carefully. These would be combined with the summary that @Centril already made. (Exactly what form this review takes I'm not sure, but I personally would consider trying a collaborative summary document. --nikomatsakis)
  • Some portion of the concerns had to do with the practical impact of making this change: i.e., making it harder for editors and other sorts of tooling to parse Rust. This at least can be evaluated by landing the change on nightly and actively updating the various bits of tooling we have, as well as allowing the change to "sit" for some time in an unstable state so that we accumulate experience.

@rfcbot rfcbot removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Jan 24, 2020
@estebank
Copy link
Contributor

For historical purposes, the current output for the example shown in the description is:

error: comparison operators cannot be chained
  --> src/main.rs:12:7
   |
12 |     id<u32>(0u32); // ok
   |       ^^^^^
   |
help: use `::<...>` instead of `<...>` to specify type arguments
   |
12 |     id::<u32>(0u32); // ok
   |       ^^

error: comparison operators cannot be chained
  --> src/main.rs:13:19
   |
13 |     let n = Nooper<&str>(":)"); // ok
   |                   ^^^^^^
   |
help: use `::<...>` instead of `<...>` to specify type arguments
   |
13 |     let n = Nooper::<&str>(":)"); // ok
   |                   ^^

error: comparison operators cannot be chained
  --> src/main.rs:14:11
   |
14 |     n.noop<()>(()); // ok
   |           ^^^^
   |
help: use `::<...>` instead of `<...>` to specify type arguments
   |
14 |     n.noop::<()>(()); // ok
   |           ^^

whereas the output when this RFC was created was:

error: chained comparison operators require parentheses
  --> <source>:12:7
   |
12 |     id<u32>(0u32); // ok
   |       ^^^^^^
   |
   = help: use `::<...>` instead of `<...>` if you meant to specify type arguments
   = help: or use `(...)` if you meant to specify fn arguments

error: chained comparison operators require parentheses
  --> <source>:13:19
   |
13 |     let n = Nooper<&str>(":)"); // ok
   |                   ^^^^^^^
   |
   = help: use `::<...>` instead of `<...>` if you meant to specify type arguments
   = help: or use `(...)` if you meant to specify fn arguments

error: chained comparison operators require parentheses
  --> <source>:14:11
   |
14 |     n.noop<()>(()); // ok
   |           ^^^^^
   |
   = help: use `::<...>` instead of `<...>` if you meant to specify type arguments
   = help: or use `(...)` if you meant to specify fn arguments

error[E0423]: expected value, found builtin type `u32`
  --> <source>:12:8
   |
12 |     id<u32>(0u32); // ok
   |        ^^^ not a value

error[E0423]: expected value, found builtin type `str`
  --> <source>:13:21
   |
13 |     let n = Nooper<&str>(":)"); // ok
   |                     ^^^ did you mean `std`?

error[E0369]: binary operation `<` cannot be applied to type `fn(_) -> _ {id::<_>}`
  --> <source>:12:5
   |
12 |     id<u32>(0u32); // ok
   |     ^^^^^^
   |
   = note: an implementation of `std::cmp::PartialOrd` might be missing for `fn(_) -> _ {id::<_>}`

error[E0369]: binary operation `<` cannot be applied to type `fn(_) -> Nooper<_> {Nooper<_>::{{constructor}}}`
  --> <source>:13:13
   |
13 |     let n = Nooper<&str>(":)"); // ok
   |             ^^^^^^^^^^^
   |
   = note: an implementation of `std::cmp::PartialOrd` might be missing for `fn(_) -> Nooper<_> {Nooper<_>::{{constructor}}}`

error[E0610]: `bool` is a primitive type and therefore doesn't have fields
  --> <source>:14:7
   |
14 |     n.noop<()>(()); // ok
   |       ^^^^

error[E0308]: mismatched types
  --> <source>:14:15
   |
14 |     n.noop<()>(()); // ok
   |               ^^^^ expected bool, found ()
   |
   = note: expected type `bool`
              found type `()`

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A-syntax Syntax related proposals & ideas A-type-application Type application related proposals & ideas 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.