Skip to content

Commit aa1d6b7

Browse files
committed
Propose DWIM for numeric fallback interaction.
1 parent 295be8b commit aa1d6b7

File tree

1 file changed

+53
-49
lines changed

1 file changed

+53
-49
lines changed

text/0000-default-type-parameter-fallback-take-two.md

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn func<P: AsRef<Path>>(p: Option<P>) {
6868

6969
If we call `func(None)` then the type of `P` cannot be inferred. This is frustrating as in this case neither the caller nor the callee care about the type of `P`, any type would do. Default parameters allow the callee to choose a suitable default, making optional generic arguments ergonomically viable.
7070

71-
We may guess this is the most often occurring use case for default arguments. [It](https://github.com/PistonDevelopers/conrod/pull/626) [comes](https://github.com/gtk-rs/gir/issues/143) [up](https://github.com/jwilm/json-request/issues/1) [a lot](https://github.com/rust-lang/rust/issues/24857). We need type parameters defaults for optional arguments to be a well supported pattern, and even more so of we wish to dream of having optional arguments as a first-class language feature.
71+
We may guess this is the most often occurring use case for default arguments. [It comes](https://github.com/gtk-rs/gir/issues/143) [up](https://github.com/jwilm/json-request/issues/1) [a lot](https://github.com/rust-lang/rust/issues/24857). We need type parameters defaults for optional arguments to be a well supported pattern, and even more so of we wish to dream of having optional arguments as a first-class language feature.
7272

7373
## Backwards-compatibily extending existing types
7474

@@ -168,7 +168,7 @@ This would currently result in inference failures when trying to do `assert_ne!(
168168

169169
## Other motivations
170170

171-
- Helping type inference with too many candidate types. The famous case here is `Iterator::collect`. It is a common cause of turbofishes and type annotations because so many types implement `FromIterator`. But most of those types are niche and in the common case people just want a `Vec`. It would be nice if we could default `collect` to return a `Vec<Iterator::Item>`. Unfortunately we can't because `Iterator` is defined in `core` and `Vec` is defined in `std`. Perhaps there are similar use cases in the ecosystem.
171+
- Helping type inference with too many candidate types. The famous case here is `Iterator::collect`. It is a common cause of turbofishes and type annotations because so many types implement `FromIterator`. But most of those types are niche and in the common case people just want a `Vec`. It would be nice if we could default `collect` to return a `Vec<Iterator::Item>`. Unfortunately we can't because `Iterator` is defined in `core` and `Vec` is defined in `std`, but maybe after the portability reform we can hack around this and perhaps there are similar use cases in the ecosystem.
172172

173173
- Making an already generic parameter more generic, for example the case of [generalizing `slice::contains` over `PartialEq`](https://github.com/rust-lang/rust/pull/46934).
174174

@@ -289,7 +289,7 @@ The behaviour of partially supplied parameter lists is as per RFC 213, omited pa
289289

290290
A key part of this proposal is that inference is now aware of defaults. When we would otherwise error due to an uninferred type we instead try using the default. This is called inference fallback which is our final attempt at inference. The algorithm for doing this is essentialy the one detailed in RFC 213, with a few considerations:
291291

292-
- The interaction with literal fallback may change, see "Unresolved Questions".
292+
- The interaction with numeric literals is different, see "Interaction with numerical fallback".
293293

294294
- The algorithm did not specify what happens in eager type resolution such as the `structurally_resolve_type` method, notably used to resolve method receivers. To prevent being a hazard to a future where no longer need to eagerly resolve types, we specify that eager type resolution will not do fallback.
295295

@@ -332,6 +332,53 @@ The following things may cause inference breakage:
332332
- Adding a default to an existing type parameter may break inference because it might cause conflicts among defaults, though that should be rare in practice. If an elided default is used the risk should be even smaller.
333333
- Removing a default may of course break inference.
334334

335+
### Interaction with numerical fallback
336+
337+
There are multiple alternatives of what to do about the interaction of user fallback with numerical (and diverging) fallback. This was discussed at lenght in [this internals thread](https://internals.rust-lang.org/t/interaction-of-user-defined-and-integral-fallbacks-with-inference/2496). The possible target scenarios are:
338+
339+
1. Numerical fallback takes precedence, always.
340+
2. User fallback takes precedence over numerical fallback, always.
341+
3. DWIM: User fallback takes preference, but if it fails we try numerical fallback.
342+
343+
This RFC proposes that we go with option 3 (DWIM). The two following examples show the consequences of each alternative, example 1:
344+
345+
```rust
346+
fn foo<T=u64>(t: T) { ... }
347+
// 1. `_` is `i32`
348+
// 2. `_` is `u64`
349+
// 3. `_` is `u64`
350+
fn main() { foo::<_>(22) }
351+
```
352+
353+
Example 2:
354+
355+
```rust
356+
fn foo<T=char>(t: T) { ... }
357+
// 1. `_` is `i32`
358+
// 2. Error.
359+
// 3. `_` is `i32`
360+
fn main() { foo::<_>(22) }
361+
```
362+
363+
Option 3 gives the best results, but it would change the behaviour of (possibly) existing code such as:
364+
365+
```rust
366+
struct Foo<T = u64>(T);
367+
// `_` was i32, now it's u64.
368+
let x = Foo::<_>(0);
369+
```
370+
371+
So if crater catches cases like this we will have to phase-in by keeping option 1 and introducing the new behaviour with lints.
372+
373+
One concern raised about DWIM is whether it would be a future-compatibility hazard for the possibility of expanding to which types literals can be inferred. To illustrate:
374+
375+
```rust
376+
fn foo<T=BigInt>(t: T) { ... }
377+
fn main() { foo::<_>(22) }
378+
```
379+
380+
Currently DWIM would consider `_` to be `i32`, but in a future where literals may somehow be inferred to `BigInt`, what happens? We avoid that hazard by specifying that DWIM will prefer the user default only if it is one of the primitive numerics that work today. If/when we get this future feature, then we may decide whether to transition DWIM to also prefer non-primitive user defaults such as `BigInt` or not.
381+
335382
## Default elision
336383

337384
Default elision is the syntax `T = _` which indicates that the default is being taken from the type or trait definitions in which `T` is used. When default elision may be used for a parameter `T` but no default is set a lint is emitted to suggest writing `T =_`.
@@ -466,6 +513,8 @@ fn func<U>(foo: Foo<Vec<U>>) {}
466513

467514
# Rationale and alternatives
468515

516+
- Is there a better name for default elision? Default propagation? Default inheritance? Is there a better syntax than `A=_`?
517+
469518
- We could stabilize writing defaults in fns and impls first (without any effect) and later stabilize inference fallback, so that existing stable libraries have a period to adapt without being concerned of creating conflicts among defaults.
470519

471520
## Default elision
@@ -543,51 +592,6 @@ Soon we would be talking about things like syntax to promise a parameter has no
543592

544593
# Unresolved questions
545594

546-
The following unresolved questions should be resolved prior to stabilization, but hopefully shouldn't block the acceptance of the proposal:
547-
548-
### Interaction with numerical fallback
549-
550-
There are multiple alternatives of what to do about the interaction of user fallback with numerical (and diverging) fallback. This was discussed at lenght in [this internals thread](https://internals.rust-lang.org/t/interaction-of-user-defined-and-integral-fallbacks-with-inference/2496). The options were:
551-
552-
1. User fallback takes precedence over numerical fallback, always.
553-
2. Numerical fallback takes precedence, always.
554-
3. DWIM: User fallback takes preference, but if it fails we try numerical fallback.
555-
4. Error on any ambiguity.
556-
557-
And now a fifth option proposed by this RFC:
558-
559-
5. Error on conflicting numericals, whenever DWIM would prefer a user fallback we instead error.
560-
561-
The two following examples show the consequences of each alternative, example 1:
562-
563-
```rust
564-
fn foo<T=u64>(t: T) { ... }
565-
// 1. `_` is `u64`
566-
// 2. `_` is `i32`
567-
// 3. `_` is `u64`
568-
// 4. Error.
569-
// 5. Error.
570-
fn main() { foo::<_>(22) }
571-
```
572-
573-
Example 2:
574-
575-
```rust
576-
fn foo<T=char>(t: T) { ... }
577-
// 1. Error.
578-
// 2. `_` is `i32`
579-
// 3. `_` is `i32`
580-
// 4. Error.
581-
// 5. `_` is `i32`.
582-
fn main() { foo::<_>(22) }
583-
```
584-
585-
Option 3 gives the best results, but it may change the behaviour of existing code so it might have to be phased-in through the errors given by option 4 or 5. The consensus reached in the thread was for using option 4 to open the possibility of transitioning to 3, is that still a consensus? However option 4 seems overly restrictive, we could instead do option 5 for a smoother transition.
586-
587-
### Terminology and syntax
588-
589-
Is there a better name for default elision? Default propagation? Default inheritance? Is there a better syntax than `A=_`?
590-
591595
### Interaction with specialization
592596

593597
Consider the example that shows the behaviour of the current implemetation:
@@ -618,4 +622,4 @@ fn main() {
618622
}
619623
```
620624

621-
We need to figure the design and implementation of defaults in specialization chains. Probably we want to allow only one default for a parameter in a specialization chain.
625+
We need to figure the design and implementation of defaults in specialization chains. Probably we want to allow only one default for a parameter in a specialization chain. This needs to be resolved prior to stabilization, but hopefully shouldn't block the acceptance of the proposal.

0 commit comments

Comments
 (0)