-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: partial turbofish #2176
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
Conversation
ping @arielb1, @scottmcm, @withoutboats |
Oh, now that I see the description, I'm pretty sure this is just #1196, which was closed. |
@eddyb Seems so, I'll read that one plot ahead. |
After reading, closing this and re-opening that one would be fine for me. |
I'd love if the feature were opt in, as in you have to do |
@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. |
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 ! |
But overcompensating isn't a solution either. |
@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 |
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 :) |
@m4b in you example code, you could just do |
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 |
To make this more concrete it’s likely more parameters will be added to disassemble and analyze, which leads to more _ |
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 |
Also can the name for |
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 |
Nearly all types, and especially that includes generic parameters, are inferred already. You have no idea when seeing |
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 |
@withoutboats This RFC does not propose that you be allowed to write |
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; |
@Evrey Note: as the RFC is currently written: |
I have a concern — how does it work with default type parameter fallback? (the tracking issue is rust-lang/rust/issues/27336). |
@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. |
Ping @bluss on my last comment ^ |
text/0000-partial-turbofish.md
Outdated
`$($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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This *
in the middle of the word seems to be messing up GH's markdown -- at least in the preview. Can you remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed =)
In C++ I certainly appreciate being able to do things like |
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. |
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 |
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.) |
No, this referred to allowing |
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)). |
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 |
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>(...) // 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 @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 work Personally, I still feel that |
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 |
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 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();
// .. |
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. |
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. |
Might be easy to confuse with trait bounds, but that’s not so different from the similarity between a struct declaration, |
@nikomatsakis, @comex I've started a discussion about named type parameters here: https://internals.rust-lang.org/t/named-type-parameters/6921 |
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. |
Rendered.
In concrete terms, this RFC entails that if
turbo::<u32, _, _, _>()
andTurbo::<u32, _>::new()
typechecks, thenturbo::<u32>()
andTurbo::<u32>::new()
must as well.