Skip to content

breakage in future compiler version #1

@lcnr

Description

@lcnr

Hi, this crate/hooks-core-1.0.0 depends on a type system unsoundness in Rust which will stop compiling in future Rust versions.

It looks like hooks-core has been since updated to no longer depend on this, so that's hopefully not a major issue. The underlying unsoundness is rust-lang/rust#57893. See the minimization of the affected code from rust-lang/trait-system-refactor-initiative#253:

pub trait Hook {
    type Value;
}

pub trait ValueGat {
    type Value;
}

pub trait ErasedHook {
    type ValueGat: ?Sized + ValueGat;
}

impl<H: ?Sized + Hook> ErasedHook for H {
    type ValueGat = dyn ValueGat<Value = <H as Hook>::Value>;
}

impl<V: ?Sized + ValueGat> Hook for dyn '_ + ErasedHook<ValueGat = V> {
    type Value = <V as ValueGat>::Value;
}

Should <dyn ErasedHook<ValueGat = V> as ErasedHook>::ValueGat normalize via the impl<H: ?Sized + Hook> ErasedHook for H, resulting in dyn ValueGat<Value = <H as Hook>::Value>, or should use it use the associated type specified by the trait object itself, resulting in V?

For an example of how this ends up resulting in unsoundness, see the following code:

// depends on hooks-core = "1.0.0-alpha.10"
use hooks_core::{
    Hook, HookLifetime,
    erased_hook::{ErasedHook, ValueGat},
};

struct Whatever;
impl ValueGat<'_> for Whatever {
    type Value = ();
}

// This function normalizes `<H as ErasedHook<Args>>::ValueGat` using the impl.
fn foo<H: ?Sized + Hook<Args>, Args>(
    x: Box<<H as ErasedHook<Args>>::ValueGat>,
) -> Box<dyn for<'hook> ValueGat<'hook, Value = <H as HookLifetime<'hook, Args>>::Value>> {
    x
}


// This function normalizes `<dyn ErasedHook<Args, ValueGat = V> as ErasedHook<Args>>::ValueGat` using the
// specified associated type of the trait object, ignoring the impl and will result in an ambiguity error
// in future Rust versions.
fn bar<V: for<'hook> ValueGat<'hook>, Args>(
    x: Box<V>,
) -> Box<dyn for<'hook> ValueGat<'hook, Value = <V as ValueGat<'hook>>::Value>> {
    foo::<dyn ErasedHook<Args, ValueGat = V> + '_, Args>(x)
}

fn main() {
    bar::<Whatever, ()>(Box::new(Whatever));
}

The fact that hooks-core relied on this unsoundness is in no way your fault. The issue is that the unsoundness exists in the first place and we don't expect users to be aware of them. We do accept breaking changes to fix unsoundnesses, so this will no longer compile in future Rust versions. More specifically, this code pattern will stop to compile with the next-generation trait solver: -Znext-solver: github.com/rust-lang/rust/issues/107374.

We are also planning to fix the underlying issue in future versions of Rust, but that change is likely blocked on removing the old trait solver implementation.

Please reach out if there are any questions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions