From dada38a0048d0ab90e42b7a2855c48c6466ce734 Mon Sep 17 00:00:00 2001 From: Mazdak Date: Mon, 16 Oct 2017 12:06:32 +0200 Subject: [PATCH 1/7] rfc, partial_turbofish: initial version (prepare for more) --- text/0000-partial-turbofish.md | 217 +++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 text/0000-partial-turbofish.md diff --git a/text/0000-partial-turbofish.md b/text/0000-partial-turbofish.md new file mode 100644 index 00000000000..b288a2acf07 --- /dev/null +++ b/text/0000-partial-turbofish.md @@ -0,0 +1,217 @@ +- Feature Name: partial_turbofish +- Start Date: 2017-10-16 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Assume a function (free, inherent, trait, ...) `fn turbo< $($tparam: ident),* )>(..) -> R` involving generic types `$($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: `Type::< $($tconcrete: ty),* >::fun(..)`. + +In concrete terms, this entails that if `turbo::()` and `Turbo::::new()` typechecks, then `turbo::()` and `Turbo::::new()` must as well. + +# Motivation +[motivation]: #motivation + +When dealing with parametric polymorphism with more than one type parameter, if +the first parameter must be specified, but others may be inferred either from +the return type, arguments or the first parameter, then the developer is forced +to make the compiler happy by acknowledging that there are more type parameters that the compiler is already aware of. + +A contrived example of this is: + +```rust +use std::iter::FromIterator; +use std::iter::repeat; + +fn turbo(elt: U, n: usize) -> T +where + U: Clone, + T: FromIterator +{ + repeat(elt).take(n).collect() +} + +struct Turbo, U>(T, U); + +impl, U> Turbo { + fn new(elt: U, n: usize) -> Self + where + U: Clone + { + Turbo(turbo(elt.clone(), n), elt) + } +} + +fn main() { + // This compiles in today's Rust: + let vec = turbo::, _>(1, 1); + + // This does not, but will compile: + let vec = turbo::>(1, 1); + + // This compiles in today's Rust: + let vec = Turbo::, _>::new(1, 1); + + // This does not, but will compile: + let vec = Turbo::>::new(1, 1); +} +``` + +By letting the user omit any suffix of `_`s, the ergonomics of working with +generic types can be improved. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Currently, the line `let vec = Turbo::>::new(1, 1);` in [motivation] +results in the following errors: + +For line 29: +``` +error[E0089]: too few type parameters provided: +expected 2 type parameters, found 1 type parameter +``` + +For line 35: +``` +error[E0243]: wrong number of type arguments: expected 2, found 1 +``` + +However, in both cases, the compiler already knows that `U = u32` since this is +a requirement induced by `T: FromIterator`. + +If a user attempts to type check: `let vec = turbo::, i32>(1, 1);`, +rustc will complain that "the trait bound `Vec: FromIterator` is not satisfied". Since the only way to satisfy `Vec: FromIterator` is with `impl FromIterator for Vec`, unifying `T` with `U`, subsituted to `u32` and `i32` fails. + +Starting from `turbo::, _>(1, 1)`, the compiler can first see +that `T = Vec`. It can then see that the only way for `Vec: FromIterator` is if `U = u32`. If `turbo::, _>(true, 1)` is used, then rustc can start from `U = bool` and so it may infer from the other direction. + +In either case, the argument to the second type parameter is known which is why +rustc allows `_` instead of a concrete type. + +This RFC proposes that suffixes to a "turbofish" which consist entirely of `_` +be omittable. + +The following: + +```rust +fn main() { + let vec = turbo::, _>(1, 1); + let vec = Turbo::, _>::new(1, 1); +} +``` + +thus becomes: + +```rust +fn main() { + let vec = turbo::>(1, 1); + let vec = Turbo::>::new(1, 1); +} +``` + +This concept is named "partial turbofish" since it is partially applying +type arguments to type parameters and letting the compiler infer the rest. + +It is still an error to apply too many type arguments in a turbofish +as in: `fn foo() {} foo::()`, or to +partially apply types when the compiler can not infer the omitted types. When +the latter happens, rustc will emit error indicating that there are more type +parameters not applied that could not be inferred. A sample error message is: +`type annotations needed` in addition to: +``` +Can not infer concrete type(s) of omitted type parameter(s) X, Y, .. +in call to fn turbo with parameters: . +``` + +Some developers may end up trying to use this feature and be surprised when it +did not work. For those who this does not apply to, the documentation should +explain this feature when it explains the turbofish syntax. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## From a Haskell perspective + +In Haskell, a function: +```haskell +fun :: a -> Int -> c +fun a i c = expr +``` + +is equivalent to: + +```haskell +fun :: forall a c. a -> Int -> c +fun a i c = expr +``` + +which, in terms of [System F](https://en.wikipedia.org/wiki/System_F) is +the judgement that: +``` +|- (Λ a. Λ b. λ x1^a. λ x2^Int. λ x3^c. expr) : ∀ a. ∀ c. a -> Int -> c +``` + +Here, `Λ a.` is a lambda function taking a type, and not a term. +In Haskell, the feature `TypeApplications` lets developers partially apply +types to such lambdas. Rust also allows this via the turbofish mechanism: +`::`. Unlike Haskell, Rust does however not allow the user to +only apply some types as a prefix, but requires the user to supply all types. +`TypeApplications` in Rust is therefore an all-or-nothing proposition: "Either +you give me all the concrete types, or I will try to infer them all for you". + +## From a Rust perspective + +This RFC allows the user to only supply only supply the smallest prefix of +concrete types that rustc requires to infer the type of the other parameters in +the suffix. The suffix may therefore be omitted. The prefix may be overspecified +and the omitted suffix may be shorter than the longest possible. + +Formally: +Assume a function (free, inherent, trait, ...) `fn turbo< $($tparam: ident),* )>(..) -> R` or a type (struct/enum/..) `Turbo` involving generic types +`$($tparam: ident),*`. If while calling `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. + +## Type checking +[typeck]: #typeck + +Currently, `typeck` may at an eariler stage (prior to full unification) check +if the number of parameters at the turbofish-site (where `::` happens) and at the definition site match. With this RFC, it may no longer do so. It can at most check if more arguments were specified at the turbofish-site compared to the definition site, if that happens, the (currently used) error: +``` +error[E0087]: too many type parameters provided: +expected at most X type parameters, found Y type parameter +``` +where `X < Y` is raised. + +If fewer arguments are specified at the turbofish-site, then and at a later stage: +If typeck can infer all the unspecified arguments at the definition site, +the typeck stage passes (assuming that the specified types already unify with +the requirements of the definition site). +If type can't infer those arguments, the smallest suffix it can't infer is used +in the error message specified in [guide-level-explanation]. + +# Drawbacks +[drawbacks]: #drawbacks + ++ It *might* slow down type checking by not letting the compiler verify that the amount of type arguments applied at the call site are as many as those as the +type parameters at the definition site. Currently, such a check can be done +prior to full type checking. + +# Rationale and alternatives +[alternatives]: #alternatives + +The alternative is to not do this. This means that a papercut in deailing with +generics remains. + +# Unresolved questions +[unresolved]: #unresolved-questions + ++ How should the error messages when too many arguments are omitted look like? + ++ Is the "algorithm" specified in [typeck] sound? Does it work? Is it sufficiently +detailed? If not, it must become sufficiently detailed prior to merging. \ No newline at end of file From 451b0797d1a5d12221f3fc14b1cf67b83f8d2915 Mon Sep 17 00:00:00 2001 From: Mazdak Date: Mon, 16 Oct 2017 12:50:47 +0200 Subject: [PATCH 2/7] rfc, partial_turbofish: fixed formatting & moved stuff & improved [typeck] --- text/0000-partial-turbofish.md | 137 +++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 56 deletions(-) diff --git a/text/0000-partial-turbofish.md b/text/0000-partial-turbofish.md index b288a2acf07..b96240ad854 100644 --- a/text/0000-partial-turbofish.md +++ b/text/0000-partial-turbofish.md @@ -6,13 +6,17 @@ # Summary [summary]: #summary -Assume a function (free, inherent, trait, ...) `fn turbo< $($tparam: ident),* )>(..) -> R` involving generic types `$($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: `Type::< $($tconcrete: ty),* >::fun(..)`. - -In concrete terms, this entails that if `turbo::()` and `Turbo::::new()` typechecks, then `turbo::()` and `Turbo::::new()` must as well. +Assume a function (free, inherent, trait, ...) +`fn turbo< $($tparam: ident),* )>(..) -> R` involving generic types +`$($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: +`Type::< $($tconcrete: ty),* >::fun(..)`. + +In concrete terms, this entails that if `turbo::()` and +`Turbo::::new()` typechecks, then `turbo::()` and +`Turbo::::new()` must as well. # Motivation [motivation]: #motivation @@ -20,7 +24,8 @@ In concrete terms, this entails that if `turbo::()` and `Turbo:: When dealing with parametric polymorphism with more than one type parameter, if the first parameter must be specified, but others may be inferred either from the return type, arguments or the first parameter, then the developer is forced -to make the compiler happy by acknowledging that there are more type parameters that the compiler is already aware of. +to make the compiler happy by acknowledging that there are more type parameters +that the compiler is already aware of. A contrived example of this is: @@ -68,6 +73,38 @@ generic types can be improved. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation +## From a Haskell perspective + +In Haskell, a function: +```haskell +fun :: a -> Int -> c +fun a i c = expr +``` + +is equivalent to: + +```haskell +fun :: forall a c. a -> Int -> c +fun a i c = expr +``` + +which, in terms of [System F](https://en.wikipedia.org/wiki/System_F) is +the judgement that: +``` +|- (Λ a. Λ b. λ x1^a. λ x2^Int. λ x3^c. expr) : ∀ a. ∀ c. a -> Int -> c +``` + +Here, `Λ a.` is a lambda function taking a type, and not a term. In Haskell, the +feature `TypeApplications` lets developers partially apply types to such lambdas +as in: `fun @Int :: Int -> Int -> c`. Rust also allows this via the turbofish +mechanism: `::`. Unlike Haskell, Rust does however not allow the +user to only apply some types as a prefix, but requires the user to supply all +types. `TypeApplications` in Rust is therefore an all-or-nothing proposition: +"Either you give me all the concrete types, or I will try to infer them all for +you". + +## From a Rust perspective + Currently, the line `let vec = Turbo::>::new(1, 1);` in [motivation] results in the following errors: @@ -86,16 +123,22 @@ However, in both cases, the compiler already knows that `U = u32` since this is a requirement induced by `T: FromIterator`. If a user attempts to type check: `let vec = turbo::, i32>(1, 1);`, -rustc will complain that "the trait bound `Vec: FromIterator` is not satisfied". Since the only way to satisfy `Vec: FromIterator` is with `impl FromIterator for Vec`, unifying `T` with `U`, subsituted to `u32` and `i32` fails. +rustc will complain that "the trait bound `Vec: FromIterator` is not +satisfied". Since the only way to satisfy `Vec: FromIterator` is with +`impl FromIterator for Vec`, unifying `T` with `U`, subsituted to `u32` +and `i32` fails. Starting from `turbo::, _>(1, 1)`, the compiler can first see -that `T = Vec`. It can then see that the only way for `Vec: FromIterator` is if `U = u32`. If `turbo::, _>(true, 1)` is used, then rustc can start from `U = bool` and so it may infer from the other direction. +that `T = Vec`. It can then see that the only way for +`Vec: FromIterator` is if `U = u32`. If `turbo::, _>(true, 1)` +is used, then rustc can start from `U = bool` and so it may infer from the other +direction. In either case, the argument to the second type parameter is known which is why rustc allows `_` instead of a concrete type. -This RFC proposes that suffixes to a "turbofish" which consist entirely of `_` -be omittable. +This RFC proposes that suffixes to a "turbofish" which consist entirely of +`, _`s be omittable. The following: @@ -136,70 +179,52 @@ explain this feature when it explains the turbofish syntax. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## From a Haskell perspective - -In Haskell, a function: -```haskell -fun :: a -> Int -> c -fun a i c = expr -``` - -is equivalent to: - -```haskell -fun :: forall a c. a -> Int -> c -fun a i c = expr -``` - -which, in terms of [System F](https://en.wikipedia.org/wiki/System_F) is -the judgement that: -``` -|- (Λ a. Λ b. λ x1^a. λ x2^Int. λ x3^c. expr) : ∀ a. ∀ c. a -> Int -> c -``` - -Here, `Λ a.` is a lambda function taking a type, and not a term. -In Haskell, the feature `TypeApplications` lets developers partially apply -types to such lambdas. Rust also allows this via the turbofish mechanism: -`::`. Unlike Haskell, Rust does however not allow the user to -only apply some types as a prefix, but requires the user to supply all types. -`TypeApplications` in Rust is therefore an all-or-nothing proposition: "Either -you give me all the concrete types, or I will try to infer them all for you". - -## From a Rust perspective - This RFC allows the user to only supply only supply the smallest prefix of concrete types that rustc requires to infer the type of the other parameters in the suffix. The suffix may therefore be omitted. The prefix may be overspecified and the omitted suffix may be shorter than the longest possible. Formally: -Assume a function (free, inherent, trait, ...) `fn turbo< $($tparam: ident),* )>(..) -> R` or a type (struct/enum/..) `Turbo` involving generic types -`$($tparam: ident),*`. If while calling `turbo::< $($tconcrete: ty),* >(..)` or equivalently `Turbo::< $($tconcrete: ty),* >::fun(..)`, for a list of concrete types `$($tconcrete: ty),*`, a suffix of `$($tconcrete: ty),*` is `$( _ ),*` and +Assume a function (free, inherent, trait, ...) +`fn turbo< $($tparam: ident),* )>(..) -> R` or a type (struct/enum/..) `Turbo` +involving generic types `$($tparam: ident),*`. If while calling +`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. ## Type checking [typeck]: #typeck Currently, `typeck` may at an eariler stage (prior to full unification) check -if the number of parameters at the turbofish-site (where `::` happens) and at the definition site match. With this RFC, it may no longer do so. It can at most check if more arguments were specified at the turbofish-site compared to the definition site, if that happens, the (currently used) error: +if the number of parameters at the turbofish-site (where `::` +happens) and at the definition site match. With this RFC, it may no longer do so. +It can at most check if more arguments were specified at the turbofish-site +compared to the definition site, if that happens, the (currently used) error: ``` error[E0087]: too many type parameters provided: expected at most X type parameters, found Y type parameter ``` where `X < Y` is raised. -If fewer arguments are specified at the turbofish-site, then and at a later stage: -If typeck can infer all the unspecified arguments at the definition site, -the typeck stage passes (assuming that the specified types already unify with -the requirements of the definition site). -If type can't infer those arguments, the smallest suffix it can't infer is used -in the error message specified in [guide-level-explanation]. +This feature does not require the introduction of type lambdas for terms, +instead, it can be achieved by having the type checker relax the above specified +rule. If fewer arguments are specified at the turbofish-site, then, the compiler +inserts `len(args@turbofish-site) - len(params@definition-site)` amount of `, _`s. +From this point onwards, type checking continues as normal. + +If typeck can infer the concrete types of all the `_` type arguments, the typeck +stage passes (assuming that the specified types already unify with the +requirements of the definition site). If type can't infer concrete types of the +`_`s inserted by the compiler, the smallest suffix of those `_`s rustc can't +infer is used in the error message specified in [guide-level-explanation]. # Drawbacks [drawbacks]: #drawbacks -+ It *might* slow down type checking by not letting the compiler verify that the amount of type arguments applied at the call site are as many as those as the -type parameters at the definition site. Currently, such a check can be done ++ It *might* slow down type checking by not letting the compiler verify that +the amount of type arguments applied at the call site are as many as those as +the type parameters at the definition site. Currently, such a check can be done prior to full type checking. # Rationale and alternatives @@ -213,5 +238,5 @@ generics remains. + How should the error messages when too many arguments are omitted look like? -+ Is the "algorithm" specified in [typeck] sound? Does it work? Is it sufficiently -detailed? If not, it must become sufficiently detailed prior to merging. \ No newline at end of file ++ Is the "algorithm" specified in [typeck] sufficiently detailed? +If not, it must become sufficiently detailed prior to merging. \ No newline at end of file From a0b66c1193e360650a5a06a0be55a8d538de94e8 Mon Sep 17 00:00:00 2001 From: Mazdak Date: Fri, 19 Jan 2018 09:16:29 +0100 Subject: [PATCH 3/7] rfc, partial_turbofish: improve in general wrt. writing style, motivation, alternatives, technical details, examples, and layout. --- text/0000-partial-turbofish.md | 178 +++++++++++++++++++++++---------- 1 file changed, 125 insertions(+), 53 deletions(-) diff --git a/text/0000-partial-turbofish.md b/text/0000-partial-turbofish.md index b96240ad854..934704c5f00 100644 --- a/text/0000-partial-turbofish.md +++ b/text/0000-partial-turbofish.md @@ -11,7 +11,7 @@ Assume a function (free, inherent, trait, ...) `$($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 also applies to *turbofish*ing types (structs, enums, ..), i.e: `Type::< $($tconcrete: ty),* >::fun(..)`. In concrete terms, this entails that if `turbo::()` and @@ -70,40 +70,63 @@ fn main() { By letting the user omit any suffix of `_`s, the ergonomics of working with generic types can be improved. -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation +## API evolution -## From a Haskell perspective +[RFC 1196]: https://github.com/Centril/rfcs/blob/rfc/partial-turbofish/text/0000-partial-turbofish.md +[1196-motivation]: https://github.com/huonw/rfcs/blob/prefix-ty-param/text/0000-prefix-type-params.md#motivation -In Haskell, a function: -```haskell -fun :: a -> Int -> c -fun a i c = expr -``` +This RFC is a continuation of [RFC 1196] which talks in its +[motivation][1196-motivation] about how this feature may enable adding new, +inferrable (due to eq-constraining), type parameters to functions without +breaking code. This RFC doubles down on this argument as a good motivation. -is equivalent to: +## Improving a real world scenario for `Arbitrary` in `proptest` -```haskell -fun :: forall a c. a -> Int -> c -fun a i c = expr -``` +Let us assume the following trait from the crate `proptest` which has been +simplified a bit here: -which, in terms of [System F](https://en.wikipedia.org/wiki/System_F) is -the judgement that: -``` -|- (Λ a. Λ b. λ x1^a. λ x2^Int. λ x3^c. expr) : ∀ a. ∀ c. a -> Int -> c +```rust +pub trait Arbitrary: Sized { + type Parameters: Default; + + fn arbitrary() -> Self::Strategy { + Self::arbitrary_with(Default::default()) + } + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy; + + type Strategy: Strategy; + type ValueTree: ValueTree; +} + +pub fn any_with(args: A::Parameters) -> A::Strategy { + A::arbitrary_with(args) +} + +pub fn arbitrary_with(args: P) -> S +where + P: Default, + S: Strategy, + S::Value: ValueTree, + A: Arbitrary, +{ + A::arbitrary_with(args) +} ``` -Here, `Λ a.` is a lambda function taking a type, and not a term. In Haskell, the -feature `TypeApplications` lets developers partially apply types to such lambdas -as in: `fun @Int :: Int -> Int -> c`. Rust also allows this via the turbofish -mechanism: `::`. Unlike Haskell, Rust does however not allow the -user to only apply some types as a prefix, but requires the user to supply all -types. `TypeApplications` in Rust is therefore an all-or-nothing proposition: -"Either you give me all the concrete types, or I will try to infer them all for -you". +Semantically, the two free functions `any_with` and `arbitrary_with` do the +same thing. However, the function `arbitrary_with` has other properties +with respect to type inference than `any_with`. In many cases, it works fine +to do `arbitrary_with(..)`, but with `any_with` you often have to specify a +concrete the type variable `A`. But if you need or want to (for extra clarity) +to specify the first type for `arbitrary_with`, you now have to fill in the +blanks using `_` as in: `arbitrary_with::(..)`. If we instead +were allowed to elide those `_`s, we could instead just have one function +that could be used in both for better type inference as well as more ergonomic +turbofishing. -## From a Rust perspective +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation Currently, the line `let vec = Turbo::>::new(1, 1);` in [motivation] results in the following errors: @@ -158,18 +181,20 @@ fn main() { } ``` -This concept is named "partial turbofish" since it is partially applying +This concept is named *partial turbofish* since it is partially applying type arguments to type parameters and letting the compiler infer the rest. +When `, _` is present in a turbofish, it is instead referred to as *turbosword*. It is still an error to apply too many type arguments in a turbofish -as in: `fn foo() {} foo::()`, or to -partially apply types when the compiler can not infer the omitted types. When -the latter happens, rustc will emit error indicating that there are more type -parameters not applied that could not be inferred. A sample error message is: -`type annotations needed` in addition to: +as in: `fn foo() {} foo::()`, or to partially apply types when the +compiler can not infer the omitted types. When the latter happens, rustc will +emit error indicating that there are more type parameters not applied that +could not be inferred. A sample error message is: `type annotations needed` +in addition to: + ``` -Can not infer concrete type(s) of omitted type parameter(s) X, Y, .. -in call to fn turbo with parameters: . +Can not infer concrete type(s) of omitted type parameter(s) `X, Y, ..` +in call to `fn turbo` with parameters: ``. ``` Some developers may end up trying to use this feature and be surprised when it @@ -179,15 +204,14 @@ explain this feature when it explains the turbofish syntax. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This RFC allows the user to only supply only supply the smallest prefix of +This RFC allows the user to only supply the smallest prefix of concrete types that rustc requires to infer the type of the other parameters in the suffix. The suffix may therefore be omitted. The prefix may be overspecified and the omitted suffix may be shorter than the longest possible. -Formally: -Assume a function (free, inherent, trait, ...) -`fn turbo< $($tparam: ident),* )>(..) -> R` or a type (struct/enum/..) `Turbo` -involving generic types `$($tparam: ident),*`. If while calling +Formally: Assume a function (free, inherent, trait, ...) +`fn turbo< $($tparam: ident),* )>(..) -> R` or a generic type (struct/enum/..) +`Turbo` with type parameters `$($tparam: ident),*`. If while calling `turbo::< $($tconcrete: ty),* >(..)` or equivalently `Turbo::< $($tconcrete: ty),* >::fun(..)`, for a list of concrete types `$($tconcrete: ty),*`, a suffix of `$($tconcrete: ty),*` is `$( _ ),*` and @@ -198,20 +222,20 @@ the type checking passes, then that suffix may be omitted. Currently, `typeck` may at an eariler stage (prior to full unification) check if the number of parameters at the turbofish-site (where `::` -happens) and at the definition site match. With this RFC, it may no longer do so. -It can at most check if more arguments were specified at the turbofish-site -compared to the definition site, if that happens, the (currently used) error: +happens) and at the definition site match. With this RFC, the compiler will +instead check if more arguments were specified at the turbofish-site compared to the definition site, and if so, the the following +error (where `X < Y`) is raised: + ``` error[E0087]: too many type parameters provided: expected at most X type parameters, found Y type parameter ``` -where `X < Y` is raised. This feature does not require the introduction of type lambdas for terms, instead, it can be achieved by having the type checker relax the above specified -rule. If fewer arguments are specified at the turbofish-site, then, the compiler -inserts `len(args@turbofish-site) - len(params@definition-site)` amount of `, _`s. -From this point onwards, type checking continues as normal. +rule. If fewer arguments are specified at the turbofish-site, the compiler +mechanically fills in `len(args@turbofish-site) - len(params@definition-site)` +amount of `, _`s. From this point onwards, type checking continues as normal. If typeck can infer the concrete types of all the `_` type arguments, the typeck stage passes (assuming that the specified types already unify with the @@ -219,13 +243,24 @@ requirements of the definition site). If type can't infer concrete types of the `_`s inserted by the compiler, the smallest suffix of those `_`s rustc can't infer is used in the error message specified in [guide-level-explanation]. +With respect to default type parameter fallback, the mechanics of a partial +turbofish does not directly interact with the fallback. The compiler fills in +the remaining `, _`s. If there are fallbacks defined which can be used given +the constraints placed by the call site, then the relevant subset of `_`s will +be substituted for the specified default types. This inference happens after +inserting any necessary `, _`s. + # Drawbacks [drawbacks]: #drawbacks -+ It *might* slow down type checking by not letting the compiler verify that -the amount of type arguments applied at the call site are as many as those as -the type parameters at the definition site. Currently, such a check can be done -prior to full type checking. ++ The user will no longer be met with a hard error when new type parameters +are introduced. While this is mostly seen as an advantage, but some users may +want to get such errors. + ++ It *might* slow down type checking to a small degree by having the compiler +insert any additional `, _`s. The compiler still has to check the length of +type arguments passed at the call site against the number of type parameters at +the definition site in any case. # Rationale and alternatives [alternatives]: #alternatives @@ -233,10 +268,47 @@ prior to full type checking. The alternative is to not do this. This means that a papercut in deailing with generics remains. +Another alternative is to use the syntax `turbo::()` to get rid of +any extra `, _`s. This syntax may however be more useful for variadics. Also, +the `, ..` makes the call longer for the most common case which is when you have +one argument to add as in: `turbo::()`. Additionally, using `..` to +acknowledge that inference is going on is inconsistent with the language in +general. There is no requirement to acknowledge type inference elsewhere. +Instead, the fact that type inference is going on should itself be inferred. + # Unresolved questions [unresolved]: #unresolved-questions + How should the error messages when too many arguments are omitted look like? -+ Is the "algorithm" specified in [typeck] sufficiently detailed? -If not, it must become sufficiently detailed prior to merging. \ No newline at end of file +# In relation to other languages + +## From a Haskell perspective + +In Haskell, a function: +```haskell +fun :: a -> Int -> c +fun a i c = expr +``` + +is equivalent to: + +```haskell +fun :: forall a c. a -> Int -> c +fun a i c = expr +``` + +which, in terms of [System F](https://en.wikipedia.org/wiki/System_F) is +the judgement that: +``` +|- (Λ a. Λ b. λ x1^a. λ x2^Int. λ x3^c. expr) : ∀ a. ∀ c. a -> Int -> c +``` + +Here, `Λ a.` is a lambda function taking a type, and not a term. In Haskell, the +feature `TypeApplications` lets developers partially apply types to such lambdas +as in: `fun @Int :: Int -> Int -> c`. Rust also allows this via the turbofish +mechanism: `::`. Unlike Haskell, Rust does however not allow the +user to only apply some types as a prefix, but requires the user to supply all +types. `TypeApplications` in Rust is therefore an all-or-nothing proposition: +"Either you give me all the concrete types, or I will try to infer them all for +you". \ No newline at end of file From f6a2fbdb7020cc9c420e504d288af597bd4775ea Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 1 Mar 2018 22:19:59 +0100 Subject: [PATCH 4/7] rfc, partial_turbofish: GH didn't like italics per @nikomatsakis's review" --- text/0000-partial-turbofish.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-partial-turbofish.md b/text/0000-partial-turbofish.md index 934704c5f00..62aa690f8c2 100644 --- a/text/0000-partial-turbofish.md +++ b/text/0000-partial-turbofish.md @@ -11,7 +11,7 @@ Assume a function (free, inherent, trait, ...) `$($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 also applies to turbofishing types (structs, enums, ..), i.e: `Type::< $($tconcrete: ty),* >::fun(..)`. In concrete terms, this entails that if `turbo::()` and From a8c2be7c30fc64c11a6afdf52d5aeaf2a7dd2f8e Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 1 Mar 2018 22:53:58 +0100 Subject: [PATCH 5/7] rfc, partial_turbofish: Prior art + opt-in as alternative + spelling fix" --- text/0000-partial-turbofish.md | 53 +++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/text/0000-partial-turbofish.md b/text/0000-partial-turbofish.md index 62aa690f8c2..fe9a2fe2a01 100644 --- a/text/0000-partial-turbofish.md +++ b/text/0000-partial-turbofish.md @@ -254,7 +254,7 @@ inserting any necessary `, _`s. [drawbacks]: #drawbacks + The user will no longer be met with a hard error when new type parameters -are introduced. While this is mostly seen as an advantage, but some users may +are introduced. While this is mostly seen as an advantage, some users may want to get such errors. + It *might* slow down type checking to a small degree by having the compiler @@ -268,6 +268,8 @@ the definition site in any case. The alternative is to not do this. This means that a papercut in deailing with generics remains. +## Acknolowedging extra inferred parameters with `..` + Another alternative is to use the syntax `turbo::()` to get rid of any extra `, _`s. This syntax may however be more useful for variadics. Also, the `, ..` makes the call longer for the most common case which is when you have @@ -276,16 +278,36 @@ acknowledge that inference is going on is inconsistent with the language in general. There is no requirement to acknowledge type inference elsewhere. Instead, the fact that type inference is going on should itself be inferred. -# Unresolved questions -[unresolved]: #unresolved-questions +## Opting into partial turbofish at definition site + +In a possible amendment to this RFC, we could require that the definition site +of a function or a data constructor explicitly opt into allowing turbofish with +the proposed syntax: + +```rust +fn foo(..) { .. } +``` + +While this gives a greater degree of control to authors, it also comes with +downsides: -+ How should the error messages when too many arguments are omitted look like? +1. Assuming that partial turbofish is the desirable default behavior, authors + of generic functions are penalized by having to opt in with `= _` on every + generic parameter. -# In relation to other languages +2. More choices comes with an increased mental cost of deciding whether to + use `= _` or not, which may not be worth the additional control afforded. + +3. Opting in leaves users of generic functions and data constructors with doubt + as to whether this or that function has opted in, wherefore they must check + the documentation. This uncertainty can contribute to disturbing writing flow. + +# Prior art ## From a Haskell perspective In Haskell, a function: + ```haskell fun :: a -> Int -> c fun a i c = expr @@ -304,11 +326,16 @@ the judgement that: |- (Λ a. Λ b. λ x1^a. λ x2^Int. λ x3^c. expr) : ∀ a. ∀ c. a -> Int -> c ``` -Here, `Λ a.` is a lambda function taking a type, and not a term. In Haskell, the -feature `TypeApplications` lets developers partially apply types to such lambdas -as in: `fun @Int :: Int -> Int -> c`. Rust also allows this via the turbofish -mechanism: `::`. Unlike Haskell, Rust does however not allow the -user to only apply some types as a prefix, but requires the user to supply all -types. `TypeApplications` in Rust is therefore an all-or-nothing proposition: -"Either you give me all the concrete types, or I will try to infer them all for -you". \ No newline at end of file +Here, `Λ a.` is a lambda function taking a type, and not a term. In Haskell, +the language pragma `TypeApplications` enabled with lets developers partially +apply types to such lambdas with the syntax: `fun @Int :: Int -> Int -> c`. +Rust also allows type application via the turbofish mechanism: `::`. +Unlike Haskell, Rust does however not allow the user to only apply some types +as a prefix, but requires the user to supply all types. Type application in +Rust is therefore an all-or-nothing proposition: "Either you give me all the +concrete types, or I will try to infer them all for you". + +# Unresolved questions +[unresolved]: #unresolved-questions + ++ How should the error messages when too many arguments are omitted look like? \ No newline at end of file From ca4f860dd73bde1c101651ea7fae26e62f4fac6d Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 1 Mar 2018 22:54:40 +0100 Subject: [PATCH 6/7] rfc, partial_turbofish: improve sectioning in Prior art" --- text/0000-partial-turbofish.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/0000-partial-turbofish.md b/text/0000-partial-turbofish.md index fe9a2fe2a01..ccc4132312a 100644 --- a/text/0000-partial-turbofish.md +++ b/text/0000-partial-turbofish.md @@ -304,8 +304,6 @@ downsides: # Prior art -## From a Haskell perspective - In Haskell, a function: ```haskell From 34a66702fd9b94bf454db7dd793e739a35e3045f Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 6 Mar 2018 18:14:34 +0100 Subject: [PATCH 7/7] rfc, partial_turbofish: shamelessly plug example by @KodrAus --- text/0000-partial-turbofish.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/text/0000-partial-turbofish.md b/text/0000-partial-turbofish.md index ccc4132312a..f8ccd296466 100644 --- a/text/0000-partial-turbofish.md +++ b/text/0000-partial-turbofish.md @@ -125,6 +125,24 @@ were allowed to elide those `_`s, we could instead just have one function that could be used in both for better type inference as well as more ergonomic turbofishing. +## One important type parameter + +The function `arbitrary_with` was a case of having only one important parameter. +Having one important type parameter and other not so important ones, is a common +reason for having to write `::`. Another example is: + +```rust +fn request(index: TIndex, id: TId) + -> RequestBuilder + where TIndex: Into, + TId: Into +{ + ... +} + +let req = request::("some_index", "some_type"); +``` + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation