Skip to content

core: Rewrite docs for try_as_dyn - #162312

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
jnkel:try_as_dyn-docs
Sep 17, 2026
Merged

rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
jnkel:try_as_dyn-docs

Conversation

@jnkel

@jnkel jnkel commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Update the documentation for try_as_dyn to resolve confusion some people had about the feature, provide better examples, and promise less.

In particular, this PR updates the documentation to never promise success, allowing for spurious failures for even "simple" cases. This reflects the general consensus in https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/try_as_dyn.20potential.20problematic.20implications/near/621331808, since the precise rules of the implementation can be quite subtle and difficult to explain to users. Instead, document the function as being best-effort and able to produce false negatives, with non-exhaustive examples of situations where it produces false negatives in practice. We can always make the guarantees stricter in the future.

Also replaces the "animal-cat-dog" style example with more realistic use cases of try_as_dyn for performance and debugging.

cc @oli-obk

Rendered

Returns Some(&U) if T can be coerced to the dyn trait type U. Otherwise, it returns None.

Warning

This function is implemented on a best-effort basis. It is not always possible to determine
whether a generic type implements a trait; thus, this function may produce false negatives,
returning None even when T implements the requested trait.

try_as_dyn is guaranteed to return None if T does not implement the requested trait, but
it is never guaranteed to return Some. It is intended to be used for performance
optimizations and debugging, and try_as_dyn succeeding for a particular type should never be
relied upon for correctness (i.e. callers must behave correctly even if try_as_dyn spuriously
returns None).

Examples of false negatives

Some examples of situations where try_as_dyn::<T, dyn Trait> returns None in practice even
when T implements Trait:

  • T's impl for Trait is lifetime-dependent
  • T's impl for Trait is a builtin impl (e.g. dyn Debug implements Debug)
  • T's impl for Trait has a trait bound which requires transitively reasoning about
    lifetime-dependent or builtin impls

This list is not exhaustive. There is some detailed documentation about these limitations at
https://doc.rust-lang.org/unstable-book/library-features/try-as-dyn.html But the gist is
summarized below:

Lifetime-dependent impls

try_as_dyn does not have access to lifetime information, thus it cannot differentiate between
'static and other lifetimes and cannot reason about outlives bounds on impls. Thus it cannot
reason about impls that have 'static lifetimes or outlives bounds of any kind. ///

The following impls are lifetime-dependent and produce false negatives when used with
try_as_dyn:

# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
// impl mentions a 'static lifetime
impl<'a, T: Debug, U: Display> Trait<'a, T> for Type<'static, U> {}
# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
// impl contains an outlives bound
impl<'a, 'b, T: Debug, U: Display> Trait<'a, T> for Type<'b, U>
    where 'b: 'a {}

Impls that mention a generic parameter more than once are lifetime-depndent and produce false
negatives, even if they don't expressly mention any lifetimes:

# trait Trait<T> {}
// impl mentions T more than once, creating an implied lifetime dependence
impl<T> Trait<T> for T {}

The following impl is lifetime-independent, because even though it mentions lifetimes,
implementation of the trait is not conditional over the lifetimes:

# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
impl<'a, 'b, T: Debug, U: Display> Trait<'a, T> for Type<'b, U> {}

Impls without generic parameters at all are also lifetime-independent, as long as they contain
no 'static lifetimes.

Builtin impls

Builtin impls (like impl Debug for dyn Debug, or automatic implementations of Send and
Sync) have various obscure rules and often are not fully generic. To simplify reasoning about
what is allowed and what not, all builtin impls are rejected and will neither directly nor
indirectly contribute to a Some result.

Compile-time failures

Determining whether T can be coerced to the dyn trait type U requires compiler trait resolution.
In some cases, that resolution can exceed the recursion limit,
and compilation will fail instead of this function returning None.

The input type T must outlive the lifetime 'a on the dyn Trait + 'a.
This is basically the same rule that forbids let x: &dyn Trait + 'static = &&some_local_variable;
So if you see borrow check errors around try_as_dyn, think about whether a normal unsizing
coercion would be possible at all if you were using concrete types or had bounds on the input type.

Examples

Using try_as_dyn to use bytewise comparison instead of PartialEq for certain types, similar to
the standard library's optimization for slices:

#![feature(try_as_dyn)]
                                                                                                     
use core::any::try_as_dyn;
                                                                                                     
/// Compares two objects for equality,
fn eq<T: PartialEq + ?Sized>(x: &T, y: &T) -> bool {
    if try_as_dyn::<T, dyn BytewiseEq>(&x).is_some() {
        // T implements BytewiseEq, so we cast the slices to u8 and compare their bytes
        // instead of calling PartialEq on each individual element.
        unsafe {
            // SAFETY: x and y are valid for reads of size_of::<T>() bytes
            // BytewiseEq trait guarantees we can interperet these bytes as u8's
            // and compare them for equality
            let x = &*core::ptr::slice_from_raw_parts(
                (&raw const *x).cast::<u8>(),
                core::mem::size_of_val(x),
            );
            let y = &*core::ptr::slice_from_raw_parts(
                (&raw const *y).cast::<u8>(),
                core::mem::size_of_val(y),
            );
                                                                                                     
            x == y
        }
    } else {
        // T does not implement BytewiseEq, or try_as_dyn returned a false negative.
        // Fallback to PartialEq.
        //
        // BytewiseEq guarantees bytewise comparison and PartialEq will produce the same
        // results, so our code behaves correctly if try_as_dyn produces false negatives.
        x == y
    }
}
                                                                                                     
/// Marker trait for types that can be compared for equality
/// using a bytewise comparison (i.e. memcmp).
///
/// Implementations must ensure the type contains no uninitialized bytes,
/// and that a bytewise comparison will produce the same result as PartialEq.
unsafe trait BytewiseEq {}
                                                                                                     
unsafe impl BytewiseEq for u8 {}
unsafe impl BytewiseEq for u16 {}
unsafe impl BytewiseEq for u32 {}
                                                                                                     
// u16 implements BytewiseEq, so eq::<u16> will use bytewise comparison
// (unless try_as_dyn returns a false negative)
assert!(eq(&5u16, &5u16));
                                                                                                     
// f32 does not implement BytewiseEq, so eq::<f32> will use element-wise comparison
assert!(eq(&5f32, &5f32));

Using try_as_dyn for debugging:

#![feature(try_as_dyn)]
                                                                                                     
use core::any::{try_as_dyn, type_name};
use core::fmt::Debug;
                                                                                                     
/// Prints a value of type T, attempting to use its Debug implementation with try_as_dyn.
fn debug_println<T: ?Sized>(x: &T) {
    if let Some(debug) = try_as_dyn::<T, dyn Debug>(x) {
        println!("{:?}", debug);
    } else {
        // T does not implement Debug, or try_as_dyn returned a false negative.
        // Print the name of the type instead.
        //
        // We're not relying on this for correctness; it's just for debugging,
        // so we can tolerate false negatives.
        println!("<{}>", type_name::<T>());
    }
}
                                                                                                     
/// This type does not implement Debug.
struct NoDebug;
                                                                                                     
// Prints "Hello, world!" unless try_as_dyn returns a false negative.
debug_println(&"Hello, world!");
                                                                                                     
// Prints the name of the type, since it does not have a Debug implementation.
debug_println(&NoDebug);
                                                                                                     
// The current implementation of try_as_dyn gives a false positive in this case!
debug_println(&"Hello, world!" as &dyn Debug);

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Sep 4, 2026
@rustbot

rustbot commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

r? @JohnTitor

rustbot has assigned @JohnTitor.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: libs
  • libs expanded to 12 candidates
  • Random selection from Darksonn, JohnTitor, Mark-Simulacrum, clarfonthey

@rust-log-analyzer

This comment has been minimized.

@jnkel
jnkel force-pushed the try_as_dyn-docs branch 2 times, most recently from 8626105 to 793a9ea Compare September 5, 2026 06:24
@oli-obk oli-obk assigned oli-obk and unassigned JohnTitor Sep 14, 2026
Comment thread library/core/src/any.rs Outdated
@oli-obk

oli-obk commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

@bors squash msg="core: Rewrite docs for try_as_dyn"

@rust-bors

This comment has been minimized.

@rust-bors

rust-bors Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

🔨 2 commits were squashed into ed74429.

@oli-obk

oli-obk commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

hm damn, that made it coauthored by me :( I was hoping it would just be your commit. Oh well. lgtm, you may want to force push the commit so it is just yours

@oli-obk

oli-obk commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@bors r+ rollup

@rust-bors

rust-bors Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 58c9d2f has been approved by oli-obk

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 17, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 17, 2026
core: Rewrite docs for try_as_dyn

Update the documentation for `try_as_dyn` to resolve confusion some people had about the feature, provide better examples, and promise less.

In particular, this PR updates the documentation to never promise success, allowing for spurious failures for even "simple" cases. This reflects the general consensus in https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/try_as_dyn.20potential.20problematic.20implications/near/621331808, since the precise rules of the implementation can be quite subtle and difficult to explain to users. Instead, document the function as being best-effort and able to produce false negatives, with non-exhaustive examples of situations where it produces false negatives in practice. We can always make the guarantees stricter in the future.

Also replaces the "animal-cat-dog" style example with more realistic use cases of `try_as_dyn` for performance and debugging.

cc @oli-obk

<details><summary>Rendered</summary>

Returns `Some(&U)` if `T` can be coerced to the dyn trait type `U`. Otherwise, it returns `None`.

> **Warning**
>
> This function is implemented on a best-effort basis. It is not always possible to determine
whether a generic type implements a trait; thus, this function may produce false negatives,
returning `None` even when `T` implements the requested trait.
>
> `try_as_dyn` is guaranteed to return `None` if `T` does *not* implement the requested trait, but
it is never guaranteed to return `Some`. It is intended  to be used for performance
optimizations and debugging, and `try_as_dyn` succeeding for a particular type should never be
relied upon for correctness (i.e. callers must behave correctly even if `try_as_dyn` spuriously
returns `None`).

</div>

# Examples of false negatives

Some examples of situations where `try_as_dyn::<T, dyn Trait>` returns `None` in practice even
when `T` implements `Trait`:
* `T`'s impl for `Trait` is lifetime-dependent
* `T`'s impl for `Trait` is a builtin impl (e.g. `dyn Debug` implements `Debug`)
* `T`'s impl for `Trait` has a trait bound which requires transitively reasoning about
lifetime-dependent or builtin impls

This list is not exhaustive. There is some detailed documentation about these limitations at
<https://doc.rust-lang.org/unstable-book/library-features/try-as-dyn.html> But the gist is
summarized below:

## Lifetime-dependent impls

`try_as_dyn` does not have access to lifetime information, thus it cannot differentiate between
`'static` and other lifetimes and cannot reason about outlives bounds on impls. Thus it cannot
reason about impls that have `'static` lifetimes or outlives bounds of any kind. ///

The following impls are lifetime-dependent and produce false negatives when used with
`try_as_dyn`:

```rust
# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
// impl mentions a 'static lifetime
impl<'a, T: Debug, U: Display> Trait<'a, T> for Type<'static, U> {}
```

```
# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
// impl contains an outlives bound
impl<'a, 'b, T: Debug, U: Display> Trait<'a, T> for Type<'b, U>
    where 'b: 'a {}
```

Impls that mention a generic parameter more than once are lifetime-depndent and produce false
negatives, even if they don't expressly mention any lifetimes:

```rust
# trait Trait<T> {}
// impl mentions T more than once, creating an implied lifetime dependence
impl<T> Trait<T> for T {}
```

The following impl is lifetime-**independent**, because even though it *mentions* lifetimes,
implementation of the trait is not *conditional* over the lifetimes:
```rust
# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
impl<'a, 'b, T: Debug, U: Display> Trait<'a, T> for Type<'b, U> {}
```

Impls without generic parameters at all are also lifetime-independent, as long as they contain
no `'static` lifetimes.

## Builtin impls

Builtin impls (like `impl Debug for dyn Debug`, or automatic implementations of `Send` and
`Sync`) have various obscure rules and often are not fully generic. To simplify reasoning about
what is allowed and what not, all builtin impls are rejected and will neither directly nor
indirectly contribute to a `Some` result.

# Compile-time failures
Determining whether `T` can be coerced to the dyn trait type `U` requires compiler trait resolution.
In some cases, that resolution can exceed the recursion limit,
and compilation will fail instead of this function returning `None`.

The input type `T` must outlive the lifetime `'a` on the `dyn Trait + 'a`.
This is basically the same rule that forbids `let x: &dyn Trait + 'static = &&some_local_variable;`
So if you see borrow check errors around `try_as_dyn`, think about whether a normal unsizing
coercion would be possible at all if you were using concrete types or had bounds on the input type.

# Examples

Using `try_as_dyn` to use bytewise comparison instead of PartialEq for certain types, similar to
the standard library's optimization for slices:

```rust
#![feature(try_as_dyn)]

use core::any::try_as_dyn;

/// Compares two objects for equality,
fn eq<T: PartialEq + ?Sized>(x: &T, y: &T) -> bool {
    if try_as_dyn::<T, dyn BytewiseEq>(&x).is_some() {
        // T implements BytewiseEq, so we cast the slices to u8 and compare their bytes
        // instead of calling PartialEq on each individual element.
        unsafe {
            // SAFETY: x and y are valid for reads of size_of::<T>() bytes
            // BytewiseEq trait guarantees we can interperet these bytes as u8's
            // and compare them for equality
            let x = &*core::ptr::slice_from_raw_parts(
                (&raw const *x).cast::<u8>(),
                core::mem::size_of_val(x),
            );
            let y = &*core::ptr::slice_from_raw_parts(
                (&raw const *y).cast::<u8>(),
                core::mem::size_of_val(y),
            );

            x == y
        }
    } else {
        // T does not implement BytewiseEq, or try_as_dyn returned a false negative.
        // Fallback to PartialEq.
        //
        // BytewiseEq guarantees bytewise comparison and PartialEq will produce the same
        // results, so our code behaves correctly if try_as_dyn produces false negatives.
        x == y
    }
}

/// Marker trait for types that can be compared for equality
/// using a bytewise comparison (i.e. memcmp).
///
/// Implementations must ensure the type contains no uninitialized bytes,
/// and that a bytewise comparison will produce the same result as PartialEq.
unsafe trait BytewiseEq {}

unsafe impl BytewiseEq for u8 {}
unsafe impl BytewiseEq for u16 {}
unsafe impl BytewiseEq for u32 {}

// u16 implements BytewiseEq, so eq::<u16> will use bytewise comparison
// (unless try_as_dyn returns a false negative)
assert!(eq(&5u16, &5u16));

// f32 does not implement BytewiseEq, so eq::<f32> will use element-wise comparison
assert!(eq(&5f32, &5f32));
```

Using `try_as_dyn` for debugging:

```rust
#![feature(try_as_dyn)]

use core::any::{try_as_dyn, type_name};
use core::fmt::Debug;

/// Prints a value of type T, attempting to use its Debug implementation with try_as_dyn.
fn debug_println<T: ?Sized>(x: &T) {
    if let Some(debug) = try_as_dyn::<T, dyn Debug>(x) {
        println!("{:?}", debug);
    } else {
        // T does not implement Debug, or try_as_dyn returned a false negative.
        // Print the name of the type instead.
        //
        // We're not relying on this for correctness; it's just for debugging,
        // so we can tolerate false negatives.
        println!("<{}>", type_name::<T>());
    }
}

/// This type does not implement Debug.
struct NoDebug;

// Prints "Hello, world!" unless try_as_dyn returns a false negative.
debug_println(&"Hello, world!");

// Prints the name of the type, since it does not have a Debug implementation.
debug_println(&NoDebug);

// The current implementation of try_as_dyn gives a false positive in this case!
debug_println(&"Hello, world!" as &dyn Debug);
```
</details>
rust-bors Bot pushed a commit that referenced this pull request Sep 17, 2026
…uwer

Rollup of 24 pull requests

Successful merges:

 - #161596 (coretests: Add more pattern tests.)
 - #162177 (Properly implement the gpu-kernel ABI for amdgpu)
 - #162411 (Make Receiver `#[rustc_dyn_incompatible_trait]`)
 - #162760 (yeet alias new_from_def_id)
 - #162796 (libtest: do not early exit from test runners)
 - #162844 (Add loan reachability traces to polonius MIR dumps)
 - #162876 (Move operations out of `rustc_middle::query::job`)
 - #160108 (Stabilize `windows_process_extensions_main_thread_handle`)
 - #160212 (traits: Fix rigid alias liveness matching)
 - #160544 (Stabilize `feature(trim_prefix_suffix)` (`{str, [T], Path}::trim_prefix` and `{str, [T]}::trim_suffix`))
 - #161305 (Use the entire type of a dropped local to compute variance (edge direction) for Polonius alpha)
 - #161838 (tests: accept LLVM 24 optimization in this test)
 - #162312 (core: Rewrite docs for try_as_dyn)
 - #162785 (Avoid creating overlapping assignments in MatchBranchSimplification)
 - #162805 (Add `must_use` lint to `ExitCode`)
 - #162825 (core: Add examples for `debug_closure_helpers`)
 - #162841 (enable asm tests for xtensa targets)
 - #162842 (reintroduce check RibKind::ConstParamTy did in direct consts)
 - #162845 (mgca: fix issue with mismatched array valtree/valtree tys)
 - #162856 (Stabilize CommandExt::show_window)
 - #162865 (Complex conjugate, negation and default)
 - #162874 (Add support for `annotate_snippets::snippet::AnnotationKind::Visible`)
 - #162881 (Simplify the macro for forwarding Decoder methods )
 - #162888 (Fix a typo on the Armv7-R platform docs page)
rust-bors Bot pushed a commit that referenced this pull request Sep 17, 2026
…uwer

Rollup of 23 pull requests

Successful merges:

 - #161596 (coretests: Add more pattern tests.)
 - #162411 (Make Receiver `#[rustc_dyn_incompatible_trait]`)
 - #162760 (yeet alias new_from_def_id)
 - #162796 (libtest: do not early exit from test runners)
 - #162844 (Add loan reachability traces to polonius MIR dumps)
 - #162876 (Move operations out of `rustc_middle::query::job`)
 - #160108 (Stabilize `windows_process_extensions_main_thread_handle`)
 - #160212 (traits: Fix rigid alias liveness matching)
 - #160544 (Stabilize `feature(trim_prefix_suffix)` (`{str, [T], Path}::trim_prefix` and `{str, [T]}::trim_suffix`))
 - #161305 (Use the entire type of a dropped local to compute variance (edge direction) for Polonius alpha)
 - #161838 (tests: accept LLVM 24 optimization in this test)
 - #162312 (core: Rewrite docs for try_as_dyn)
 - #162785 (Avoid creating overlapping assignments in MatchBranchSimplification)
 - #162805 (Add `must_use` lint to `ExitCode`)
 - #162825 (core: Add examples for `debug_closure_helpers`)
 - #162841 (enable asm tests for xtensa targets)
 - #162842 (reintroduce check RibKind::ConstParamTy did in direct consts)
 - #162845 (mgca: fix issue with mismatched array valtree/valtree tys)
 - #162856 (Stabilize CommandExt::show_window)
 - #162865 (Complex conjugate, negation and default)
 - #162874 (Add support for `annotate_snippets::snippet::AnnotationKind::Visible`)
 - #162881 (Simplify the macro for forwarding Decoder methods )
 - #162888 (Fix a typo on the Armv7-R platform docs page)
@rust-bors
rust-bors Bot merged commit 4259d00 into rust-lang:main Sep 17, 2026
13 checks passed
@rustbot rustbot added this to the 1.100.0 milestone Sep 17, 2026
rust-bors Bot pushed a commit that referenced this pull request Sep 17, 2026
Rollup merge of #162312 - jnkel:try_as_dyn-docs, r=oli-obk

core: Rewrite docs for try_as_dyn

Update the documentation for `try_as_dyn` to resolve confusion some people had about the feature, provide better examples, and promise less.

In particular, this PR updates the documentation to never promise success, allowing for spurious failures for even "simple" cases. This reflects the general consensus in https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/try_as_dyn.20potential.20problematic.20implications/near/621331808, since the precise rules of the implementation can be quite subtle and difficult to explain to users. Instead, document the function as being best-effort and able to produce false negatives, with non-exhaustive examples of situations where it produces false negatives in practice. We can always make the guarantees stricter in the future.

Also replaces the "animal-cat-dog" style example with more realistic use cases of `try_as_dyn` for performance and debugging.

cc @oli-obk

<details><summary>Rendered</summary>

Returns `Some(&U)` if `T` can be coerced to the dyn trait type `U`. Otherwise, it returns `None`.

> **Warning**
>
> This function is implemented on a best-effort basis. It is not always possible to determine
whether a generic type implements a trait; thus, this function may produce false negatives,
returning `None` even when `T` implements the requested trait.
>
> `try_as_dyn` is guaranteed to return `None` if `T` does *not* implement the requested trait, but
it is never guaranteed to return `Some`. It is intended  to be used for performance
optimizations and debugging, and `try_as_dyn` succeeding for a particular type should never be
relied upon for correctness (i.e. callers must behave correctly even if `try_as_dyn` spuriously
returns `None`).

</div>

# Examples of false negatives

Some examples of situations where `try_as_dyn::<T, dyn Trait>` returns `None` in practice even
when `T` implements `Trait`:
* `T`'s impl for `Trait` is lifetime-dependent
* `T`'s impl for `Trait` is a builtin impl (e.g. `dyn Debug` implements `Debug`)
* `T`'s impl for `Trait` has a trait bound which requires transitively reasoning about
lifetime-dependent or builtin impls

This list is not exhaustive. There is some detailed documentation about these limitations at
<https://doc.rust-lang.org/unstable-book/library-features/try-as-dyn.html> But the gist is
summarized below:

## Lifetime-dependent impls

`try_as_dyn` does not have access to lifetime information, thus it cannot differentiate between
`'static` and other lifetimes and cannot reason about outlives bounds on impls. Thus it cannot
reason about impls that have `'static` lifetimes or outlives bounds of any kind. ///

The following impls are lifetime-dependent and produce false negatives when used with
`try_as_dyn`:

```rust
# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
// impl mentions a 'static lifetime
impl<'a, T: Debug, U: Display> Trait<'a, T> for Type<'static, U> {}
```

```
# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
// impl contains an outlives bound
impl<'a, 'b, T: Debug, U: Display> Trait<'a, T> for Type<'b, U>
    where 'b: 'a {}
```

Impls that mention a generic parameter more than once are lifetime-depndent and produce false
negatives, even if they don't expressly mention any lifetimes:

```rust
# trait Trait<T> {}
// impl mentions T more than once, creating an implied lifetime dependence
impl<T> Trait<T> for T {}
```

The following impl is lifetime-**independent**, because even though it *mentions* lifetimes,
implementation of the trait is not *conditional* over the lifetimes:
```rust
# trait Trait<'a, T> {}
# struct Type<'b, U>(&'b U);
# use std::fmt::{Debug, Display};
impl<'a, 'b, T: Debug, U: Display> Trait<'a, T> for Type<'b, U> {}
```

Impls without generic parameters at all are also lifetime-independent, as long as they contain
no `'static` lifetimes.

## Builtin impls

Builtin impls (like `impl Debug for dyn Debug`, or automatic implementations of `Send` and
`Sync`) have various obscure rules and often are not fully generic. To simplify reasoning about
what is allowed and what not, all builtin impls are rejected and will neither directly nor
indirectly contribute to a `Some` result.

# Compile-time failures
Determining whether `T` can be coerced to the dyn trait type `U` requires compiler trait resolution.
In some cases, that resolution can exceed the recursion limit,
and compilation will fail instead of this function returning `None`.

The input type `T` must outlive the lifetime `'a` on the `dyn Trait + 'a`.
This is basically the same rule that forbids `let x: &dyn Trait + 'static = &&some_local_variable;`
So if you see borrow check errors around `try_as_dyn`, think about whether a normal unsizing
coercion would be possible at all if you were using concrete types or had bounds on the input type.

# Examples

Using `try_as_dyn` to use bytewise comparison instead of PartialEq for certain types, similar to
the standard library's optimization for slices:

```rust
#![feature(try_as_dyn)]

use core::any::try_as_dyn;

/// Compares two objects for equality,
fn eq<T: PartialEq + ?Sized>(x: &T, y: &T) -> bool {
    if try_as_dyn::<T, dyn BytewiseEq>(&x).is_some() {
        // T implements BytewiseEq, so we cast the slices to u8 and compare their bytes
        // instead of calling PartialEq on each individual element.
        unsafe {
            // SAFETY: x and y are valid for reads of size_of::<T>() bytes
            // BytewiseEq trait guarantees we can interperet these bytes as u8's
            // and compare them for equality
            let x = &*core::ptr::slice_from_raw_parts(
                (&raw const *x).cast::<u8>(),
                core::mem::size_of_val(x),
            );
            let y = &*core::ptr::slice_from_raw_parts(
                (&raw const *y).cast::<u8>(),
                core::mem::size_of_val(y),
            );

            x == y
        }
    } else {
        // T does not implement BytewiseEq, or try_as_dyn returned a false negative.
        // Fallback to PartialEq.
        //
        // BytewiseEq guarantees bytewise comparison and PartialEq will produce the same
        // results, so our code behaves correctly if try_as_dyn produces false negatives.
        x == y
    }
}

/// Marker trait for types that can be compared for equality
/// using a bytewise comparison (i.e. memcmp).
///
/// Implementations must ensure the type contains no uninitialized bytes,
/// and that a bytewise comparison will produce the same result as PartialEq.
unsafe trait BytewiseEq {}

unsafe impl BytewiseEq for u8 {}
unsafe impl BytewiseEq for u16 {}
unsafe impl BytewiseEq for u32 {}

// u16 implements BytewiseEq, so eq::<u16> will use bytewise comparison
// (unless try_as_dyn returns a false negative)
assert!(eq(&5u16, &5u16));

// f32 does not implement BytewiseEq, so eq::<f32> will use element-wise comparison
assert!(eq(&5f32, &5f32));
```

Using `try_as_dyn` for debugging:

```rust
#![feature(try_as_dyn)]

use core::any::{try_as_dyn, type_name};
use core::fmt::Debug;

/// Prints a value of type T, attempting to use its Debug implementation with try_as_dyn.
fn debug_println<T: ?Sized>(x: &T) {
    if let Some(debug) = try_as_dyn::<T, dyn Debug>(x) {
        println!("{:?}", debug);
    } else {
        // T does not implement Debug, or try_as_dyn returned a false negative.
        // Print the name of the type instead.
        //
        // We're not relying on this for correctness; it's just for debugging,
        // so we can tolerate false negatives.
        println!("<{}>", type_name::<T>());
    }
}

/// This type does not implement Debug.
struct NoDebug;

// Prints "Hello, world!" unless try_as_dyn returns a false negative.
debug_println(&"Hello, world!");

// Prints the name of the type, since it does not have a Debug implementation.
debug_println(&NoDebug);

// The current implementation of try_as_dyn gives a false positive in this case!
debug_println(&"Hello, world!" as &dyn Debug);
```
</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants