Skip to content

Commit ae17524

Browse files
committed
Add unstable book entry
1 parent c6abcad commit ae17524

2 files changed

Lines changed: 82 additions & 1 deletion

File tree

library/core/src/any.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,11 @@ pub trait TryAsDynCompatible<T>: ptr::Pointee<Metadata = ptr::DynMetadata<Self>>
995995
/// * `T`'s impl for `Trait` is not fully generic,
996996
/// * `T`'s impl for `Trait` is a builtin impl (e.g. `dyn Debug` implements `Debug`)
997997
///
998-
/// ## Fully generic impls
998+
/// There is some detailed documentation about this feature at
999+
/// https://doc.rust-lang.org/unstable-book/language-features/try_as_dyn.html
1000+
/// But the gist is summarized below:
1001+
///
1002+
/// ## Lifetime-independent impls
9991003
///
10001004
/// `try_as_dyn` does not have access to lifetime information, thus it cannot differentiate between
10011005
/// `'static`, other lifetimes, and can't reason about outlives bounds on impls. Thus we can only accept
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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

Comments
 (0)