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

RFC: partial turbofish #2176

Closed
wants to merge 7 commits into from

Conversation

Projects
None yet
@Centril
Copy link
Contributor

Centril commented Oct 16, 2017

Rendered.

In concrete terms, this RFC entails that if turbo::<u32, _, _, _>() and Turbo::<u32, _>::new() typechecks, then turbo::<u32>() and Turbo::<u32>::new() must as well.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 16, 2017

@eddyb

This comment has been minimized.

Copy link
Member

eddyb commented Oct 16, 2017

Oh, now that I see the description, I'm pretty sure this is just #1196, which was closed.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 16, 2017

@eddyb Seems so, I'll read that one plot ahead.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 16, 2017

After reading, closing this and re-opening that one would be fine for me.
If those involved in #1196 want to continue on with this one, that is also fine.

@est31

This comment has been minimized.

Copy link
Contributor

est31 commented Oct 16, 2017

I'd love if the feature were opt in, as in you have to do turbo::<u32, ..>() instead of turbo::<u32, _, _, _>(). This would be consistent to other parts of the language like tuple patterns.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 16, 2017

@est31 If one were to pick, I'm strongly in favour of turbo::<u32>(), but you could also allow both turbo::<u32, ..>() and turbo::<u32>(). To me, turbo::<u32>(), is better wrt. API evolution as discussed in the motivation of #1196.

@est31

This comment has been minimized.

Copy link
Contributor

est31 commented Oct 16, 2017

@Centril I don't think allowing both would be useful, either do turbo::<u32>() or turbo::<u32, ..>().

API evolution is much better solved by doing type defaults on the generic parameters you add, which you can omit already now. I mean when you add a generic param to a function, the type param must have been a concrete type previously. Just add that as default and it works.

On the other hand, if you add a generic param via type inference, some of your code can now silently use a new type, and behaviour might change. Also, the compiler allows breakage in inference from one compiler version to the next.

@scottmcm scottmcm added the T-lang label Oct 17, 2017

@m4b

This comment has been minimized.

Copy link

m4b commented Oct 18, 2017

I was literally complaining about this on irc the other day, so +1 for me! I actually can’t +1 this enough.

Forcing users to supply _ for extra generic parameters when the compiler literally already knows it is so beaurocratically draconian it’s hard for me to relate why this is desirable, and as the document notes makes working with complex genetics significantly more burdensome.

Furthermore I just don’t buy readability or “silent usage” concerns; generic parameter defaults already opened that panadoras box imho so any concerns for client side usage of generics (which is really what the turbo fish does) apply equally to generic parameter defaults.

Also on that note:

  1. I agree should def not be both. And I definitely prefer no ..; it should mirror generic defaults which allows their omission, ie I’m in favor of what seems to me the natural behavior of supplying subsequent inferred type arguments to the turbofish.

  2. Generic defaults are not a solution to this problem in general. Sometimes you can’t control the upstream crate. And sometimes a generic default is not appropriate at all. This is about ergonomics when calling a generic function and I think the proposal here nails that aspect.

I have a good example of when inferring the type is very natural and expected behavior (but it’s not, because it doesn’t work that way) I’ll post here when I have some more, but I also think the motivating examples are really great. So yea, great work and I’m hoping a version of this makes it through !

@est31

This comment has been minimized.

Copy link
Contributor

est31 commented Oct 18, 2017

This is about ergonomics when calling a generic function and I think the proposal here nails that aspect.

But overcompensating isn't a solution either. .. improves ergonomics pretty well, while preserving the feature that you know whether there is some inference going on.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 18, 2017

@m4b While writing turbo::<X, _> is annoying, I wouldn't go as far as to say it's "beaurocratically draconian". That's a bit much.

I actually think the motivation part of the RFC is quite thin on examples. I'd love to have more of them to add from real code and not just my off-the-top-off-my-head examples.

@est31
I don't buy the "This would be consistent to other parts of the language like tuple patterns." argument - while .. is natural for destructuring and patterns, I find it annoying for types and turbofish. I find it much more convincing that what is inferred for types shouldn't be mentioned, and that inference occurs should also be inferred.

In my opinion .. hurts ergonomics since it actually makes it longer for the (probably) most common case, having a single _ at the end.

Unless there is a technical reason why .. is required - for example in relation to default generic type parameters - I see no reason to change the RFC to use .. instead, unless there's a heavy consensus that this is more ergonomic.

@m4b

This comment has been minimized.

Copy link

m4b commented Oct 18, 2017

While writing turbo::<X, _> is annoying, I wouldn't go as far as to say it's "beaurocratically draconian". That's a bit much.

Haha sorry, yea sometimes I'm a bit much 😆

This is an example off top of my head I just ran into and which annoyed me, I'll inline it here:

fn disassemble<Function: Fun + DataFlow + Send>(binary: &str) -> Result<Program<Function>> {
    let (mut proj, machine) = loader::load(Path::new(&binary))?;
    let program = proj.code.pop().unwrap();
    let reg = proj.region().clone();
    info!("disassembly thread started");
    Ok(match machine {
        Machine::Avr => analyze::<avr::Avr, Function>(program, reg.clone(), avr::Mcu::atmega103()),
        Machine::Ia32 => analyze::<amd64::Amd64, Function>(program, reg.clone(), amd64::Mode::Protected),
        Machine::Amd64 => analyze::<amd64::Amd64, Function>(program, reg.clone(), amd64::Mode::Long),
    }?)
}

Adding Function as the trailing parameter to analyze is just tedious (beaurocracy ;)); it's already annotated in the function parameter both as a generic and in the return type, and will be reified when called by a client via turbofish, e.g., analyze::<neo::Function>.

Imho, it just looks beautiful, intuitive and awesome without the extra annotation inside the body, in analyze.

I don't buy the "This would be consistent to other parts of the language like tuple patterns." argument - while .. is natural for destructuring and patterns, I find it annoying for types and turbofish. I find it much more convincing that what is inferred for types shouldn't be mentioned, and that inference occurs should also be inferred.

I agree with this completely. I also believe it would add to confusion, as when people will discover generic defaults, they will ask, why are those allowed to be left out when calling a function without a .. diaresis, but turbofish requires ..?

Imho, there's a natural correlation and synergy between omission of generic defaults and omission of inferred types in turbofish, and I personally would look forward to very cool patterns and chains of generics that it would allow.

Anyway, I'm just repeating myself now more or less, besides example, so yea :)

@cristicbz

This comment has been minimized.

Copy link

cristicbz commented Oct 18, 2017

@m4b in you example code, you could just do ::analyze<avr::Avr, _> etc, right? You don't need to spell out function, right?

@m4b

This comment has been minimized.

Copy link

m4b commented Oct 18, 2017

Yes, that’s what this issue is about, omitting inferred parameters

EDIT

@cristicbz I just realized are you asking whether it will compile with _ ? If so I don’t know that’s a good question. Seems to me it should obviously infer the type there but haven’t tried

@m4b

This comment has been minimized.

Copy link

m4b commented Oct 18, 2017

To make this more concrete it’s likely more parameters will be added to disassemble and analyze, which leads to more _

@Evrey

This comment has been minimized.

Copy link

Evrey commented Oct 18, 2017

I have an other reason not to go with x::<T, ..>:

Const generics are coming in the near future. A next possible step is to maybe have variadic generics comparable to C++'s variadic templates. I'd like to see that .. in generics being reserved for that. At least for me, variadic generics would be the first thing coming to my mind when guessing what x::<T,..> is supposed to mean. This also has a nice symmetry with va_list.

An other proposal for those who like it explicit:

We'll have x::<'_, T> for those wanting to infer a lifetime parameter. Why not expand the meaning of _ for "I don't give a damn", so that any x::<T, _, _, _, _, _ /*etc. ...*/> can be written as a mere x::<T, _>. rustc would accept _ here as an: "At least one infered generic argument."

@m4b

This comment has been minimized.

Copy link

m4b commented Oct 18, 2017

Also can the name for ::<F, _, _, _>and friends be the turbo sword ?

@est31

This comment has been minimized.

Copy link
Contributor

est31 commented Oct 18, 2017

I also believe it would add to confusion, as when people will discover generic defaults, they will ask, why are those allowed to be left out when calling a function without a .. diaresis, but turbofish requires ..?

There is also a difference. Default type parameters are never inferred. Either way, I'm not a super strong supporter of .. vs omitting the arguments.

An other proposal for those who like it explicit: [...] rustc would accept _ here as an: "At least one infered generic argument."

No, that sounds like a very bad idea, as it would be even more inconsistent with what _ means in other places. In fact I'd prefer if there was a lint for _ trailing parameters if we go with the proposal by @Centril .

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 18, 2017

@Evrey The argument regarding variadic generics seems reasonable.

@m4b And it was called the Turbo Sword until the end of time.

@est31 Linting seems like a great idea.

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Oct 18, 2017

while preserving the feature that you know whether there is some inference going on.

Nearly all types, and especially that includes generic parameters, are inferred already. You have no idea when seeing foo.bar() if bar had some type parameters that have been inferred. What is the significance of presenting this information in this rare case where you're using a turbofish?

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Oct 18, 2017

To me, the downside of this feature is mainly that I might make some bad assumptions: e.g. let's say I see .collect::<Result<Vec<_>>>(). I tend to assume when I see Result with some parameter that there's been a type alias that specifies the error, and I might go looking for it. But in fact it could be that the error type could just be inferred.

But maybe after this feature exists, I would grow used to writing Result<Vec<_>> instead of Result<Vec<_>, _>, and I wouldn't make an assumption like that anymore.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 18, 2017

@withoutboats This RFC does not propose that you be allowed to write Result<Vec<_>> instead of Result<Vec<_>, _>. It only applies to function application and not _ in type constructors.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 18, 2017

Correction: This RFC also applies to _ in type constructors, but only when also part of turbofish (which implies function application).

In other words, it applies to:

Turbo::<Vec<u32>, _>::new(1, 1);
// becomes:
Turbo::<Vec<u32>>::new(1, 1);

but not:

let x : Turbo<Vec<u32>, _> = expr;
// will not become:
let x : Turbo<Vec<u32>> = expr;
@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Oct 25, 2017

@Evrey Note: as the RFC is currently written: x::<T, _, _, _, _, _ /*etc. ...*/> can be rewritten as x::<T> or x::<T, _> or x::<T, _, _> or x::<T, _, _, _> and so on.

@bluss

This comment has been minimized.

Copy link

bluss commented Oct 28, 2017

I have a concern — how does it work with default type parameter fallback? (the tracking issue is rust-lang/rust/issues/27336).

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Nov 3, 2017

@bluss So; Repeating what I said on IRC a while back:

Since this RFC is mostly about a syntactic transformation that can be run before type inference, the idea is that if any function or type has default type parameter fallback, and inference is not otherwise constrained, then the expanded _ in a certain position, will use default type parameter.

An example: Say we have a function foo with 5 type parameters, 3 of which are defaulted. The 2 first parameters are specified in turbofish. The 3rd is constrained to be non-default and not specified in turbofish. The 2 last parameters are defaulted and not specified in turbofish. The compiler will expand this call foo::<A, B>(..) to: foo::<A, B, _, _, _>(..). Since this transformation done purely on the length of type parameter list by inserting missing _s, then type inference can replace _ in a given position with the default, or some other type that must be used.

If this sounds reasonable, I'll add a note about this in the RFC.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Dec 30, 2017

Ping @bluss on my last comment ^

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Jan 19, 2018

I've now updated the RFC with better stuff in general =)

`turbo::< $($tconcrete: ty),* >(..)` or equivalently
`Turbo::< $($tconcrete: ty),* >::fun(..)`, for a list of concrete types
`$($tconcrete: ty),*`, a suffix of `$($tconcrete: ty),*` is `$( _ ),*` and
the type checking passes, then that suffix may be omitted.

This comment has been minimized.

@petrochenkov

petrochenkov Jan 20, 2018

Contributor

I usually seen these called "path arguments in value context", as I understand this RFC touches only them.

  • Value context in (function) bodies is foo::<_>(a, b)/Vec::<_>::foo/etc - 1) _ can be inferred, 2) missing arguments are replaced with _s (before this RFC:) if no explicit arguments are provided or (after this RFC:) always, and 3) how exactly _s are inferred is an orthogonal question, the inference can be enhanced with fallback based on default type parameters.
  • Type context in (function) bodies is let x: Vec<_>/x.collect::<Vec<_>>/etc - 1) _ still can be inferred, 2) missing arguments are not replaced with _s but can be replaced by default type parameters, _s always have to be written manually, and 3) how exactly _s are inferred is an orthogonal question, the inference can be enhanced with fallback based on default type parameters.
  • Type context in signatures is fn f(arg: Vec<_>)/const C: Vec<_> - 1) _ cannot be inferred and is prohibited, 2) missing arguments are not replaced with _s but can be replaced by default type parameters, and 3) _s cannot be inferred as previously mentioned.

This comment has been minimized.

@petrochenkov

petrochenkov Jan 20, 2018

Contributor

It would be interesting to apply the idea from this RFC (replacing missing arguments with _s) to the second case as well. Imagine how nice would be to write

x.collect::<Vec>()
let y: Vec = x.collect();

but that would be more challenging from backward-compatibility point of view, unfortunately.

This comment has been minimized.

@petrochenkov

petrochenkov Jan 20, 2018

Contributor

FWIW, I support alway replacing missing type arguments with _s for the case 1, as this RFC suggests.
This is certainly an ergonomic win, it's future-compatible with fallback-related inference changes and I've never seen this causing problems in C++.

This comment has been minimized.

@Centril

Centril Jan 20, 2018

Author Contributor

I think @withoutboats was opposed to allowing:

x.collect::<Vec>()
let y: Vec = x.collect();

There could be some problems relating to future HKT proposals.
We can certainly revisit this with an RFC at some point, but I prefer to only allow this in turbofish at this point in time.

leodasvacas added a commit to leodasvacas/rfcs that referenced this pull request Feb 3, 2018

`$($tparam: ident),*`. If while calling `turbo::< $($tconcrete: ty),* >(...)` a
suffix of the applied types can be replaced with a list of `_`s of equal length,
then the suffix may be omitted entirely. A shorter suffix may be chosen at will.
This also applies to *turbofish*ing types (structs, enums, ..), i.e:

This comment has been minimized.

@nikomatsakis

nikomatsakis Mar 1, 2018

Contributor

This * in the middle of the word seems to be messing up GH's markdown -- at least in the preview. Can you remove it?

This comment has been minimized.

@Centril

Centril Mar 1, 2018

Author Contributor

Removed =)

@scottmcm

This comment has been minimized.

Copy link
Member

scottmcm commented Mar 1, 2018

In C++ I certainly appreciate being able to do things like castlike_thing<T>(x) even though that function takes two parameters. But that implies things like the return type should always be the first generic argument, which already isn't the case on things like transmute...

@joshtriplett

This comment has been minimized.

Copy link
Member

joshtriplett commented Mar 1, 2018

We discussed this in the lang team meeting. There was some concern about the degree of implicitness. There might be some merit to the proposal of opting into this in the type definition (e.g. defining fn foo<T, U = _, V = _> to allow foo::<SomeType> without the , _, _), though there wasn't consensus there; at a minimum, though, that should likely be included as an alternative. But there was not consensus that this RFC should be accepted. We also had concerns about interactions with default type parameters and about consistency between turbofish and other instances where type parameters appear.

We'll follow up with additional next steps.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Mar 1, 2018

@joshtriplett

We also had concerns about interactions with default type parameters

Please elaborate on this part since I have no idea what the problem could be...
My understanding is that filling in _s would be done before inference, and that defaults would affect how type inference behaves by providing fallbacks.

There might be some merit to the proposal of opting into this in the type definition

I'm happy to include any and all alternatives; I've included some text about this possibility and what I think about it; A summary of my initial views are:

With respect to requiring fn foo<T, U = _, V = _> that seems to penalize default position (I assume it is, but I could be wrong..) that = _ is right in most cases. Granted, opting into partial turbofish is better than no partial turbofish at all, but still... I'm curious to know what the use cases for not wanting partial turbofish are? Also, I believe that opt-in will lead to a less consistent experience for library users as they will not have to check documentation to see if a function or data constructor has opted in or not - I believe this uncertainty to be a drawback.

We also had concerns about interactions with default type parameters and about consistency between turbofish and other instances where type parameters appear.

I assume this refers to not allowing Vec instead of Vec<_> and Result<Vec> instead of Result<Vec<_>, _>? I'm open to that, but we'd have to think carefully about the interactions with possible higher kinded types in the future.

@scottmcm

This comment has been minimized.

Copy link
Member

scottmcm commented Mar 2, 2018

default type parameters

I think the concern is that it makesMyType<T> in the function signature mean something different from MyType::<T> in an expression path, if the former is defaulting and the latter is inferring.

AFAIK having infers and defaults simultaneously is really hard, or at least we don't have a good implementation strategy for it at this time. (Similar to the troubles with coercions, iirc.)

@joshtriplett

This comment has been minimized.

Copy link
Member

joshtriplett commented Mar 2, 2018

I assume this refers to not allowing Vec instead of Vec<> and Result instead of Result<Vec<>, _>? I'm open to that, but we'd have to think carefully about the interactions with possible higher kinded types in the future.

No, this referred to allowing Type<Foo> instead of Type<Foo, _, _>.

@petrochenkov

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Mar 2, 2018

@scottmcm

I think the concern is that it makes MyType<T> in the function signature mean something different from MyType::<T> in an expression path, if the former is defaulting and the latter is inferring.

That's exactly the current rules extended to more cases, missing generic arguments are already defaulted in "type context" including signatures and inferred in "value contexts" (#2176 (comment)).

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Mar 3, 2018

@scottmcm

I think the concern is that it makes MyType<T> in the function signature mean something different from MyType::<T> in an expression path, if the former is defaulting and the latter is inferring.

I'm confused.. This RFC does not affect inference or defaulting at all.

All it would do in this particular context is that you'd be able to write:

fn main() {
    struct MyType;
    struct Foo<T, U>(T, U);
    Foo::<u32>(1, MyType);
}

If you have a problem in mind, can you illustrate with an example?

@joshtriplett

No, this referred to allowing Type<Foo> instead of Type<Foo, _, _>.

But this RFC does not currently propose that you should be able to write Type<Foo> instead of Type<Foo, _, _>... Can you elaborate on the problem you see?

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 4, 2018

I didn't see anybody bring it up yet, but I see a connection between this RFC and impl Trait. Presently, we have a limitation such that in a function that uses both explicit type parameters and impl Trait, you cannot use the turbofish operator:

fn foo<T>(x: impl Debug) { }

...

foo::<u32>(...) // error

This restriction came out of a conversation between @petrochenkov and I where it was apparent that we had different expectations, so we decided to postpone the problem. In particular, my expectation is that I would want to write code above, where impl Debug is not treated as a type parameter that a user can manually specify -- those ought to be written out distinctly.

@petrochenkov, in contrast, was arguing that there ought to be some sort of transition path for a function that is using type parameters today, so that it can use impl Trait without disturbing existing users who may be using turbofish (obviously this would only sometimes apply):

fn bar<T: Debug>(x: T) { .. } // today
fn bar(x: impl Debug) { .. } // tomorrow

bar::<u32>(...) // continues to work

Personally, I still feel that impl Trait parameters ought not to be specifiable via turbofish: but it's interesting that even if we did allow them to be, this RFC would enable you to ignore them as a user if you wanted.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 4, 2018

I have to say that I personally am becoming somewhat fond of the opt-in variant of this RFC:

// the `_` means `T` can be elided at the call site:
fn foo<T = _>(...)

But in general I feel like there is a bit of a 'morass' of things I would like to see more harmonized:

  • Defaults on types, which operate indiscriminantly
  • Inference fallback such as @leodasvacas proposed in #2321
  • (Analogous, though distinct) the desire for optional parameters in functions

The T = _ syntax can definitely be viewed as a special case of today's defaults on types: if you specify some of the type parameters, the extra ones are defaulted always, but they happen to be defaulted to an inference variable. (We could permit this on types too, actually, it would just only apply in struct literals and other "expression" contexts.)

However, in @leodasvacas's RFC, T = _ indicates a type parameter whose default is implied by the types of the parameters of the function. That is not the case here. So we start to see a bit of conflict (though perhaps a resolveable one).

I have been historically quite grumpy with how our defaults in type parameters in any case. In particular, I don't like them being a linear list. I want to give names. Consider HashMap:

pub struct HashMap<K, V, S = RandomState>

What happens if we want to add an allocator parameter (say) to HashMap? Now, in order to specify the allocator, I have to specify the S parameter too? That seems dumb. This now seems sort of connected to optional parameters in functions, of course.

One complication here is that Foo<Bar=Baz> already has a meaning, as an associated type binding, so we would probably want to be careful enabling some syntax like HashMap<Allocator = Foo> (particularly since trait type parameters probably want defaults too). Or maybe it's ok for the meaning of that to depend on whether Allocator is declared as an named optional type parameter or an associated type.

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented Mar 4, 2018

I have to say that I personally am becoming somewhat fond of the opt-in variant of this RFC:

// the `_` means `T` can be elided at the call site:
fn foo<T = _>(...)

What situation is there where, as a library author, you would not want to allow this? Would it be remotely common? I'm concerned that this would lead to every generic function in every library ever being written with T = _ parameters in order to be feature complete and enable the best experience for their users (or else have it be considered an API bug), so we would have pushed more work onto library authors and created more noise for little gain.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Mar 4, 2018

@nikomatsakis

Personally, I still feel that impl Trait parameters ought not to be specifiable via turbofish

This is my view also; but I hadn't thought of partial turbofish in this light. Thanks for bringing it to my attention.

I have to say that I personally am becoming somewhat fond of the opt-in variant of this RFC:

Here I second @glaebhoerl's point about use cases for not opt-ing in.. are there such notable cases?

(Analogous, though distinct) the desire for optional parameters in functions

I believe that if we re-imagine turbofish application site (what is inside < and > in ::<..>) as a place for implicit arguments we could support optional parameters nicely and consistently by having runtime values with defaults as in:

fn foo<x: usize = 42>() {
    println!("{}", x);
}

This is also a nice path forward if we want to pursue full dependent types.

Inference fallback such as @leodasvacas proposed in #2321
[...] However, in @leodasvacas's RFC, T = _ indicates a type parameter whose default is implied by the types of the parameters of the function. That is not the case here.

I assume this does not apply to the main proposal of this RFC to not permit opt-in and always allow omitting extra , _ whether the definition site wants to or not. AFAIK, this RFC and #2321 are fully compatible in the sense that this RFC has no effect on inference, it just mechanically inserts , _s and lets inference take on from there. The intersection of this RFC and #2321 is that the latter simply adds more cases where , _ is permissible, and so those can be elided with this RFC.

In particular, I don't like them being a linear list.

For now, I think we should improve what we can and solve the real world problems caused by having to add , _ and not having defaults.

I think that while linear lists scale very poorly, there are few cases where you have a lot of parameters that it matters not whether the linearity scales poorly or not. Tho, the HashMap example you bring up is showing in just how badly things scale. You could mitigate it with a type alias that reorders parameters or fixes to RandomState.

To solve this issue, I think we need changes or additions that are much larger than either #2321 or this RFC proposes. A possible syntax could be:

pub struct HashMap<K, V, S = RandomState, A = Heap> { .. }

let foo: HashMap<K, V, {A} = OtherAlloc> = HashMap::default();
// variations with different sigils:
let foo: HashMap<K, V, [A] = OtherAlloc> = HashMap::default();
let foo: HashMap<K, V, |A| = OtherAlloc> = HashMap::default();
let foo: HashMap<K, V, #A = OtherAlloc> = HashMap::default();

Here, {A} refers to name A as used in HashMap<K, ..> and I think it looks pretty good.

If we can infer K, V then we can also write:

let foo: HashMap<{A} = OtherAlloc> = HashMap::default();
// ..
@petrochenkov

This comment has been minimized.

Copy link
Contributor

petrochenkov commented Mar 4, 2018

@nikomatsakis

I didn't see anybody bring it up yet, but I see a connection between this RFC and impl Trait.

Data point - how C++ does this:

template<typename T, typename U>
void f_long(T x, U y) {}

// Shortcut for the long form, can be `auto` (aka `impl Anyhing`) or `Concept` (aka `impl Trait`)
void f_short(auto x, auto y) {}

int main() {
    f_long<int, int>(0, 1); // Both are explicitly specified
    f_long<int>(0, 1); // One is explicitly specified, another is inferred
    f_long(0, 1); // Both are inferred
    
    f_short<int, int>(0, 1); // Both are explicitly specified
    f_short<int>(0, 1); // One is explicitly specified, another is inferred
    f_short(0, 1); // Both are inferred
}

http://coliru.stacked-crooked.com/a/2cea20ce31e64e54

I.e. arguments for parameters defined with "impl Trait" can be provided explicitly and trailing parameters are inferred if not provided, so the short form is simply a sugar for the long form.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 5, 2018

@glaebhoerl

What situation is there where, as a library author, you would not want to allow this

Hmm, a fair question. =) I had imagined you would only want to allow it in cases where you expect turbofish to be used -- i.e., if there is a kind of "primary" type parameter that you expect users to have to specify. But I admit I can't think of a strong reason not to permit it.

@comex

This comment has been minimized.

Copy link

comex commented Mar 5, 2018

HashMap<K, V, A: OtherAlloc>?

Might be easy to confuse with trait bounds, but that’s not so different from the similarity between a struct declaration, struct Foo { a: T } and the syntax for a value, Foo { a: val }.

@Centril

This comment has been minimized.

Copy link
Contributor Author

Centril commented Mar 5, 2018

@nikomatsakis, @comex I've started a discussion about named type parameters here: https://internals.rust-lang.org/t/named-type-parameters/6921

@aturon

This comment has been minimized.

Copy link
Member

aturon commented Mar 15, 2018

Note that @Centril, @leodasvacas and some others are now taking up discussion related to default type parameters on functions, turbofish, and more -- hopefully resulting in a new RFC. As such, I'm going to close this one for the time being.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.