You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: text/0000-default-type-parameter-fallback-take-two.md
+69-9Lines changed: 69 additions & 9 deletions
Original file line number
Diff line number
Diff line change
@@ -70,6 +70,8 @@ If we call `func(None)` then the type of `P` cannot be inferred. This is frustra
70
70
71
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.
72
72
73
+
Note that this example is a fourtunate case, see "Addendum: Getting generic `Option` arguments to work" at the bottom for an analysis of less fourtunate, and yet common, cases.
74
+
73
75
## Backwards-compatibily extending existing types
74
76
75
77
It's perfectly backwards-compatible for a type to grow new private fields with time. However if that field is generic over a new type parameter, trouble arises. The big use case in std is extending collections to be parametric over a custom allocator. Again something that must be backwards compatible and that most users don't care about. The existing feature was successful in making `HashMap` parametric over the hasher, so it has merits but it could be improved. To understand this let's try a simplified attempt at making `Arc` parametric over an allocator ([a real attempt](https://github.com/rust-lang/rust/pull/45272)).
Then `use_my_arc(make_my_arc(0))` works but now we broke `use_my_arc(Arc::from_raw(ptr))`. So the only reasonable choice is to use the default in the type definition. Therefore we use an elided default in this situation, using the the default in the type definition as the default for `A`.
421
+
Then `use_my_arc(make_my_arc(0))` works but now we broke, for example, `use_my_arc(Arc::from_raw(ptr))`. So the only reasonable choice is to use the default in the type definition. Therefore we use an elided default in this situation, using the the default in the type definition as the default for `A`.
419
422
420
423
```rust
421
424
// The default of `A` in these declarations is `alloc::Heap`
422
-
fnmake_my_arc<T, A=_>(t:T) ->Arc<T, A> {}
423
-
fnuse_my_arc<T, A=_>(arc:Arc<T, A>) {}
425
+
fnmake_my_arc<T, A:Alloc+Default=_>(t:T) ->Arc<T, A> {}
426
+
fnuse_my_arc<T, A:Alloc=_>(arc:Arc<T, A>) {}
424
427
```
425
428
426
429
It can be difficult to reason about whether a type parameter can use an elided default. To help usability we lint when a parameter that may have an elided default does not have a default. In rare cases this lint may be a false positive. But this doesn't seem bad as `#[allow(default_not_elided)]` will serve as an indication that a default is purposefully not set.
@@ -623,3 +626,60 @@ fn main() {
623
626
```
624
627
625
628
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.
629
+
630
+
## Addendum: Getting generic `Option` arguments to work
631
+
632
+
Improving generic `Option` arguments is perhaps the big motivation of this RFC. However the example given in the motivation section glossed over some other common difficulties. The example was similar to:
633
+
634
+
```rust
635
+
// A default is necessary otherwise `func(None)` cannot infer a type for `P`.
636
+
fnfunc<P:AsRef<Path> =str>(p:Option<&P>) {
637
+
matchp {
638
+
None=> { /* do something */ }
639
+
Some(path) => { /* do something else */ }
640
+
}
641
+
}
642
+
```
643
+
644
+
And it would work fine. But in many cases we wish to somehow give a default value in case of `None`, which we might naively attempt as:
645
+
646
+
```rust
647
+
fnfunc<P:AsRef<Path> =str>(p:Option<&P>) {
648
+
// This does not (and should not) type check,
649
+
// because "p == None" does not imply "P = str".
650
+
letp_or_default=p.unwrap_or(&"/default/path");
651
+
}
652
+
```
653
+
654
+
What now? There are multiple ways to make this work. A simple way is to use a trait object instead, if possible. Either in the argument type itself or in the body of the function, examples:
655
+
656
+
```rust
657
+
// This works today (modulo dyn Trait syntax).
658
+
fnfunc(p:Option<&dynAsRef<Path>>) {
659
+
letp_or_default=p.unwrap_or(&"/default/path");
660
+
}
661
+
// Some cases may want to convert to a trait object inside the body.
662
+
fnfunc<P:AsRef<Path> =str>(p:Option<&P>) {
663
+
letp_or_default:&dynAsRef<Path> =
664
+
ifletSome(p) =p { p } else { &"/default/path" };
665
+
}
666
+
```
667
+
668
+
Trait objects have limitations, so they might not work for all cases. A flexible but more convoluted way is to use an auxiliary function, like this:
669
+
670
+
```rust
671
+
fnfunc<P:AsRef<Path> =str>(p:Option<&P>) {
672
+
ifletSome(p) =p {
673
+
inner_func(p)
674
+
} else {
675
+
inner_func(&"/default/path")
676
+
};
677
+
678
+
fninner_func<P:AsRef<Path>>(p:&P) {
679
+
/* actually do something with p */
680
+
}
681
+
}
682
+
```
683
+
684
+
We need type parameter defaults to help these things work, but are these patterns reasonable? Or are they just too complex?
0 commit comments