|
| 1 | +# `try_as_dyn` |
| 2 | + |
| 3 | +The tracking issue for this feature is: [#144361] |
| 4 | + |
| 5 | +[#144361]: https://github.com/rust-lang/rust/issues/144361 |
| 6 | + |
| 7 | +------------------------ |
| 8 | + |
| 9 | +The `try_as_dyn` feature allows going from a generic `T` with no bounds |
| 10 | +to a `dyn Trait`, if `T: Trait` and various conditions are upheld. It is |
| 11 | +very related to specialization, as it allows you to specialize within |
| 12 | +function bodies, but in a more general manner than `Any::downcast`. |
| 13 | + |
| 14 | +```rust |
| 15 | +#![feature(try_as_dyn)] |
| 16 | + |
| 17 | +fn downcast_debug_format<T: 'static>(t: &T) -> String { |
| 18 | + match std::any::try_as_dyn::<_, dyn Debug>(t) { |
| 19 | + Some(d) => format!("{d:?}"), |
| 20 | + None => "default".to_string() |
| 21 | + } |
| 22 | +} |
| 23 | +``` |
| 24 | + |
| 25 | + |
| 26 | +## Rules and reasons for them |
| 27 | + |
| 28 | +> [!IMPORTANT] |
| 29 | +> The main problem of **`try_as_dyn` and specialization is the compiler's inability, while trait-checking, to distinguish/_discriminate_ between any two given lifetimes**[^1]. |
| 30 | +
|
| 31 | +[^1]: the compiler cannot _branch_ on whether "`'a : 'b` holds": for soundness, it can either choose not to know the answer, or _assume_ that it holds and produce an obligation for the borrow-checker which shall "assert this" (making compilation fail in a fatal manner if not). Most usages of Rust lie in the latter category (typical `where` clauses anywhere), whilst specialization/`try_as_dyn()` wants to support fallibility of the operation (_i.e._, being queried on a type not fulfilling the predicate without causing a compilation error). This rules out the latter, resulting in the need for the former, _i.e._, for the `try_as_dyn()` attempt to unconditionally "fail" with `None`. |
| 32 | + |
| 33 | +### `'static` is not mentioned anywhere in the `impl` block header. |
| 34 | + |
| 35 | +The most obvious one: if you have `impl IsStatic for &'static str`, then determining whether `&'? str : IsStatic` does hold amounts to discriminating `'? : 'static`. |
| 36 | + |
| 37 | +### Each outlives `where` bound (`Type: 'a` and `'a: 'b`) does not mention lifetime-infected parameters. |
| 38 | + |
| 39 | +We can create lifetime discrimination this way. For instance, given `impl<'a, 'b> Outlives<'a> for &'b str where 'b : 'a {}`, `Outlives<'static>` amounts to `IsStatic` from previous bullet. |
| 40 | + |
| 41 | +### Each lifetime-infected parameter is mentioned at most once in the `Self` type and the implemented trait's generic parameters, combined. |
| 42 | + |
| 43 | +Repetition of a parameter entails equality of those two use-sites; in lifetime-terms, this would be a double `'a : 'b` / `'b : 'a` clause, for instance. |
| 44 | +Follow-up from the previous example: `impl<'a> Uses<'a> for &'a str {}`, and check whether `&'? str : Uses<'static>`. |
| 45 | + |
| 46 | +### Each individual trait where bound (`Type: Trait`) mentions each lifetime-infected parameter at most once. |
| 47 | + |
| 48 | +Mentioning a lifetime-infected parameter in multiple `where` bounds is allowed. |
| 49 | + |
| 50 | +Looking at the previous rules, which focuses on `Self : …`, this is just observing that shifting the requirements to other parameters within `where` clauses \[ought to\] boil down to the same set of issues. |
| 51 | + |
| 52 | +This is _unnecessarily restrictive_: we should be able to loosen it up somehow. Repetition only in `where` clauses seems fine. |
| 53 | + |
| 54 | + |
| 55 | +### The `impl` block is a handwritten impl |
| 56 | + |
| 57 | +as opposed to a type implementing a trait automatically by the compiler (such as auto-traits, `dyn Bounds… : Bounds…`, and closures) |
| 58 | + |
| 59 | + |
| 60 | +The reason for this is that some such auto-generated impls _come with hidden bounds or whatnot_, which run afoul of the previous rules, whilst also being _extremely challenging for the current compiler logic to know of such bounds_. |
| 61 | +IIUC, this restriction could be lifted in the future should the compiler logic be better at spotting these hidden bounds, when present. |
| 62 | + |
| 63 | +One contrived such example being the case of `dyn 'u + for<'a> Outlives<'a>`, where the compiler-generated `impl` for it of `Outlives` is: `impl<'b, 'u> Outlives<'b> for dyn 'u + for<'a> Outlives<'a> where 'b : 'u {}` which violates the "`'a: 'b` not to mention lt-infected params" rule, whilst also being hard to detect in current compiler logic. |
| 64 | + |
| 65 | +### Associated type projections (`<Type as Trait>::Assoc`) are not mentioned anywhere in the `impl` block header. |
| 66 | + |
| 67 | +Checking whether `&'? str: Trait` discriminates `'?` against `'static` in the following scenario, for instance: |
| 68 | + |
| 69 | +```rust |
| 70 | +trait Assoc { type StaticStr; } |
| 71 | +impl Assoc for () { type StaticStr = &'static str; } |
| 72 | +impl Trait for <() as Assoc>::StaticStr {} |
| 73 | +``` |
| 74 | + |
| 75 | +### Each trait `where` bound with an associated type equality (`Type: Trait<Assoc = Type2>`) does not mention lifetime-infected parameters. |
| 76 | + |
| 77 | +Checking whether `Option<&'? str>: IntoIterator<Item = &'static str>` holds discriminates `'?` against `'static`. |
0 commit comments