Skip to content

Commit 166024a

Browse files
committed
Refactoring, Bounding refined items
1 parent 409f3dd commit 166024a

File tree

1 file changed

+90
-2
lines changed

1 file changed

+90
-2
lines changed

text/3245-refined-impls.md

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ Because we allow writing impls that look refined, but are [not usable][not-usabl
194194

195195
### Lint against unmarked refined impls
196196

197-
After this RFC is merged, we should warn when a user writes an impl that looks refined and suggest that they copy the exact API of the trait they are implementing. Once this feature stabilizes, we can should add and suggest using `#[refine]` attribute to mark that an impl is intentionally refined.
197+
After this RFC is merged, we should warn when a user writes an impl that looks refined and suggest that they copy the exact API of the trait they are implementing. Once this feature stabilizes, we can suggest using `#[refine]` attribute to mark that an impl is intentionally refined.
198198

199199
### Automatic migration for the next edition
200200

@@ -275,6 +275,17 @@ It is possible for a user to form an impression of a trait API by seeing its use
275275

276276
It's rarely obvious, however, that a *trait* API is being used at a call site as opposed to an inherent API (which can be completely different from one type to the next). The one place it is obvious is in generic functions, which will typically only have access to the original trait API.
277277

278+
## Refactoring
279+
[Refactoring]: #refactoring
280+
281+
When a trait API is refined by a type, users of that type may rely on refined details of that API without realizing it. This could come as a surprise when they then try to refactor their code to be generic over that type.
282+
283+
The general form of this problem isn't specific to refined impls. Making code generic always loses type information (which is the point) and often requires you to tweak some details about your implementation to compensate. This feature would add another place where that can happen. Using a const or method that was defined in a trait, even when that trait is in your generic bounds, may not be enoughyour non-generic code may have relied on a refined aspect of that item.
284+
285+
In some situations the user may realize they are relying on too many details of the concrete type and either don't want to make their code generic, or need to refactor it to be more general. In other situations, however, they may want to add extra bounds so their code can be generic without significant modifications.
286+
287+
This problem can be solved or mitigated with new ways of adding bounds to the refined items, but those are out of scope for this RFC and not fully designed. They are described below in [Bounding refined items].
288+
278289
# Rationale and alternatives
279290
[rationale-and-alternatives]: #rationale-and-alternatives
280291

@@ -366,7 +377,84 @@ impl Foo for () {
366377
let _: String = ().foo();
367378
```
368379

369-
With refinement impls, we can say that this desugaring is equivalent because return position impl trait would give the exact same flexibility as associated types.
380+
With refinement impls, we can say that this desugaring is equivalent because return position impl trait would give the same flexibility to implementers as associated types.
381+
382+
## Bounding refined items
383+
[Bounding refined items]: #bounding-refined-items
384+
385+
As described in the [Refactoring] drawbacks section, when making existing code generic a user may run into dependence on refined aspects of a concrete type not specified in the trait itself. In this case the user may want to add additional bounds so they can make their code generic without significant modifications.
386+
387+
This problem already exists for associated types, but bounds can be added for those. This implies a couple of ways to solve this problem.
388+
389+
### New kinds of bounds
390+
391+
We can make it possible to add bounds on all refine-able aspects of a trait API.
392+
393+
It is already likely we will want to allow bounding the return type of methods:
394+
395+
```rust
396+
trait Trait {
397+
fn foo() -> impl Clone;
398+
}
399+
400+
fn take_foo<T: Foo>(_: T) where T::foo: Copy { ... }
401+
```
402+
403+
The need for this arises both in `async` (e.g. needing to bound a future return type by `Send`) and in cases like `-> impl Iterator` where additional properties are required. A mechanism for supplying these bounds could possibly be extended to bounding what _argument_ types a method accepts.
404+
405+
There is no way to bound the type of a const today. It is possible one could be added, but since consts will only allow subtype refinement (i.e. a type with a longer lifetime than required by the trait) it is unlikely that this situation will come up often in practice.
406+
407+
### Falling back to associated types
408+
409+
As mentioned above, associated types can have bounds, either on their exact value or with other traits:
410+
411+
```rust
412+
fn foo<T: Iterator<Item = u32>>(_: T) { ... }
413+
fn bar<T: Iterator>(_: T) where T::Item: Clone { ... }
414+
```
415+
416+
Because associated types are the most flexible option **we may want to make it possible to add associated types to a trait backward-compatibly**. For example, given the following trait:
417+
418+
```rust
419+
trait Trait {
420+
fn foo() -> impl Clone;
421+
}
422+
```
423+
424+
we want to be able to refactor to something like this:
425+
426+
```rust
427+
trait Trait {
428+
type Foo: Clone;
429+
fn foo() -> Self::Foo;
430+
}
431+
```
432+
433+
There are least a couple of things needed for this:
434+
435+
1. Don't require implementations to specify associated type values when they can be inferred. For example:
436+
```rust
437+
trait Trait {
438+
type Foo;
439+
fn foo() -> Foo;
440+
}
441+
442+
impl Trait for () {
443+
fn foo() -> usize;
444+
// `type Foo = usize;` is not needed,
445+
// since it can be inferred from the above.
446+
}
447+
```
448+
1. Allow adding associated types without breaking existing usages of `dyn`. For example, let's say we had support for return-position `impl Trait` with dynamic dispatch. With [associated type defaults] and type alias `impl Trait`, you could write:
449+
```rust
450+
trait Trait {
451+
type Foo: Clone = impl Clone;
452+
fn foo() -> Foo;
453+
}
454+
```
455+
and allow `dyn Trait` to mean `dyn Trait<Foo = impl Clone>`.
456+
457+
[associated type defaults]: https://github.com/rust-lang/rust/issues/29661
370458

371459
## Adding generic parameters
372460

0 commit comments

Comments
 (0)