Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: partial turbofish #2176
Conversation
Centril
added some commits
Oct 16, 2017
This comment has been minimized.
This comment has been minimized.
|
ping @arielb1, @scottmcm, @withoutboats |
This comment has been minimized.
This comment has been minimized.
|
Oh, now that I see the description, I'm pretty sure this is just #1196, which was closed. |
This comment has been minimized.
This comment has been minimized.
|
@eddyb Seems so, I'll read that one plot ahead. |
This comment has been minimized.
This comment has been minimized.
|
After reading, closing this and re-opening that one would be fine for me. |
This comment has been minimized.
This comment has been minimized.
|
I'd love if the feature were opt in, as in you have to do |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@Centril I don't think allowing both would be useful, either do 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
added
the
T-lang
label
Oct 17, 2017
This comment has been minimized.
This comment has been minimized.
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 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:
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 ! |
This comment has been minimized.
This comment has been minimized.
But overcompensating isn't a solution either. |
This comment has been minimized.
This comment has been minimized.
|
@m4b While writing 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 In my opinion Unless there is a technical reason why |
This comment has been minimized.
This comment has been minimized.
m4b
commented
Oct 18, 2017
•
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 Imho, it just looks beautiful, intuitive and awesome without the extra annotation inside the body, in
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 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 :) |
This comment has been minimized.
This comment has been minimized.
cristicbz
commented
Oct 18, 2017
|
@m4b in you example code, you could just do |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
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 _ |
This comment has been minimized.
This comment has been minimized.
Evrey
commented
Oct 18, 2017
|
I have an other reason not to go with 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 An other proposal for those who like it explicit: We'll have |
This comment has been minimized.
This comment has been minimized.
m4b
commented
Oct 18, 2017
|
Also can the name for |
This comment has been minimized.
This comment has been minimized.
There is also a difference. Default type parameters are never inferred. Either way, I'm not a super strong supporter of
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 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Nearly all types, and especially that includes generic parameters, are inferred already. You have no idea when seeing |
This comment has been minimized.
This comment has been minimized.
|
To me, the downside of this feature is mainly that I might make some bad assumptions: e.g. let's say I see But maybe after this feature exists, I would grow used to writing |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats This RFC does not propose that you be allowed to write |
This comment has been minimized.
This comment has been minimized.
|
Correction: This RFC also applies to 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; |
This comment has been minimized.
This comment has been minimized.
|
@Evrey Note: as the RFC is currently written: |
This comment has been minimized.
This comment has been minimized.
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). |
This comment has been minimized.
This comment has been minimized.
|
@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 An example: Say we have a function If this sounds reasonable, I'll add a note about this in the RFC. |
petrochenkov
referenced this pull request
Nov 8, 2017
Open
Tracking issue for `invalid_type_param_default` compatibility lint #36887
This comment has been minimized.
This comment has been minimized.
|
Ping @bluss on my last comment ^ |
This comment has been minimized.
This comment has been minimized.
|
I've now updated the RFC with better stuff in general =) |
petrochenkov
reviewed
Jan 20, 2018
| `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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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.
Centril
referenced this pull request
Feb 3, 2018
Closed
Default type parameter fallback revisited #2321
leodasvacas
added a commit
to leodasvacas/rfcs
that referenced
this pull request
Feb 3, 2018
aturon
assigned
withoutboats
Feb 7, 2018
nikomatsakis
reviewed
Mar 1, 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.
This comment has been minimized.
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
In C++ I certainly appreciate being able to do things like |
This comment has been minimized.
This comment has been minimized.
|
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 We'll follow up with additional next steps. |
Centril
added some commits
Mar 1, 2018
This comment has been minimized.
This comment has been minimized.
Please elaborate on this part since I have no idea what the problem could be...
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
I assume this refers to not allowing |
This comment has been minimized.
This comment has been minimized.
I think the concern is that it makes 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.) |
This comment has been minimized.
This comment has been minimized.
No, this referred to allowing |
This comment has been minimized.
This comment has been minimized.
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)). |
This comment has been minimized.
This comment has been minimized.
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?
But this RFC does not currently propose that you should be able to write |
This comment has been minimized.
This comment has been minimized.
|
I didn't see anybody bring it up yet, but I see a connection between this RFC and fn foo<T>(x: impl Debug) { }
...
foo::<u32>(...) // errorThis 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 @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 fn bar<T: Debug>(x: T) { .. } // today
fn bar(x: impl Debug) { .. } // tomorrow
bar::<u32>(...) // continues to workPersonally, I still feel that |
This comment has been minimized.
This comment has been minimized.
|
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:
The However, in @leodasvacas's RFC, 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
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 One complication here is that |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
This is my view also; but I hadn't thought of partial turbofish in this light. Thanks for bringing it to my attention.
Here I second @glaebhoerl's point about use cases for not opt-ing in.. are there such notable cases?
I believe that if we re-imagine turbofish application site (what is inside fn foo<x: usize = 42>() {
println!("{}", x);
}This is also a nice path forward if we want to pursue full dependent types.
I assume this does not apply to the main proposal of this RFC to not permit opt-in and always allow omitting extra
For now, I think we should improve what we can and solve the real world problems caused by having to add 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 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, If we can infer let foo: HashMap<{A} = OtherAlloc> = HashMap::default();
// .. |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
comex
commented
Mar 5, 2018
|
Might be easy to confuse with trait bounds, but that’s not so different from the similarity between a struct declaration, |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis, @comex I've started a discussion about named type parameters here: https://internals.rust-lang.org/t/named-type-parameters/6921 |
This comment has been minimized.
This comment has been minimized.
|
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. |
Centril commentedOct 16, 2017
Rendered.
In concrete terms, this RFC entails that if
turbo::<u32, _, _, _>()andTurbo::<u32, _>::new()typechecks, thenturbo::<u32>()andTurbo::<u32>::new()must as well.