Skip to content

Tracking issue for RFC #2056: Allow trivial constraints to appear in where clauses #48214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 of 5 tasks
aturon opened this issue Feb 14, 2018 · 41 comments
Open
1 of 5 tasks
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-RFC-implemented Blocker: Approved by a merged RFC and implemented but not stabilized. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC F-trivial_bounds `#![feature(trivial_bounds)]` S-tracking-impl-incomplete Status: The implementation is incomplete. S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@aturon
Copy link
Member

aturon commented Feb 14, 2018

This is a tracking issue for the RFC "Allow trivial constraints to appear in where clauses " (rust-lang/rfcs#2056).

Steps:

Unresolved questions:

  • Should the lint error by default instead of warn?
@aturon aturon added B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC labels Feb 14, 2018
@matthewjasper
Copy link
Contributor

I'm working on this.

@nikomatsakis
Copy link
Contributor

@matthewjasper ok. I've not thought hard about what it will take to support this. I had thought about doing it after making some more progress on general trait refactoring, but if it can be easily supported today seems fine.

@nikomatsakis
Copy link
Contributor

Oh, I meant to add, please ping me on IRC/gitter with any questions! I can try to carve out some time to think about it as well.

bors added a commit that referenced this issue May 16, 2018
Implement RFC 2056 trivial constraints in where clauses

This is an implementation of the new behaviour for #48214. Tests are mostly updated to show the effects of this. Feature gate hasn't been added yet.

Some things that are worth noting and are maybe not want we want

* `&mut T: Copy` doesn't allow as much as someone might expect because there is often an implicit reborrow.
* ~There isn't a check that a where clause is well-formed any more, so `where Vec<str>: Debug` is now allowed (without a `str: Sized` bound).~

r? @nikomatsakis
bors added a commit that referenced this issue May 19, 2018
Prevent main from having a where clause.

Closes #50714

Should this have a crater run?

cc #48557, #48214

r? @nikomatsakis
bors added a commit that referenced this issue Jun 9, 2018
…atsakis

Re-enable trivial bounds

cc #50825

Remove implementations from global bounds in winnowing when there is ambiguity.

This results in the reverse of #24066 happening sometimes. I'm not sure if anything can be done about that though.

cc #48214

r? @nikomatsakis
@matthewjasper
Copy link
Contributor

matthewjasper commented Jun 10, 2018

So with #51042 merged this feature is implemented on nightly, but the implementation isn't particularly clean and there are some edge cases that don't work as one would expect:

#![feature(trivial_bounds)]

struct A;
trait X<T> {
    fn test(&self, t: T) {}
}

impl X<i32> for A {}

fn foo(a: A) where A: X<i64> {
    a.test(1i64); // expected i32, found i64
}

Since Chalk might be able to fix some of these issues, I think that this feature is now waiting for Chalk to be integrated.

edit: As of 2018-12-28 -Zchalk does not currently solve this issue.

@jonas-schievink jonas-schievink added B-RFC-implemented Blocker: Approved by a merged RFC and implemented but not stabilized. B-unstable Blocker: Implemented in the nightly compiler and unstable. labels Jun 9, 2019
@RalfJung
Copy link
Member

As another concrete example where trivially false constraints arise due to macros, see taiki-e/pin-project#102 (comment). That crate now carries a weird hack to work around this limitation.

What is the current status of this feature?

@matthewjasper
Copy link
Contributor

Not much has changed since my last comment.

@Systemcluster
Copy link

The current implementation generates a warning when using the pre-existing impl trait syntax, which, reading the RFC, I assume is a false-positive.

#![allow(dead_code, unused_variables,)]
#![feature(trait_alias, trivial_bounds)]

trait T = Fn(&i32, &i32) -> bool;
fn impl_trait_fn() -> impl T {
	|a: &i32, b: &i32| true
}

This code generates the warning warning: Trait bound impl T: T does not depend on any type or lifetime parameters. (playground link)

@Luro02
Copy link
Contributor

Luro02 commented Feb 16, 2020

I think this change could cause problems with some proc-macros that rely on

struct _AssertCopy where String: ::core::marker::Copy;

to not compile, like @ExpHP wrote in rust-lang/rfcs#2056

To play devil's advocate, I do have to wonder if some crates may have popped up since 1.7 that rely on these errors as a form of static assertion about what traits are implemented (as the author was likely unaware that trivially true bounds were intended to produce compiler errors). Out of concern for this, I might lean towards "warn by default", since macros can easily disable the warnings in their output.

but I can not comprehend why this should be warn by default as this would break existing proc-macros that rely on this behavior?

I can say for a fact that there are definitely crates that rely on this behavior, because I recently published my first proc-macro (shorthand), which does rely on this behavior and I saw those assertions (struct __AssertCopy where String: ::core::marker::Copy;) in some crates and adopted it into my own crate.

I think most of those assertions are only used to make better error messages.

For example my proc-macro emits

struct _AssertCopy where String: ::core::marker::Copy;

which is spanned around the field of a struct and this creates this error message:

error[E0277]: the trait bound `std::string::String: std::marker::Copy` is not satisfied
 --> $DIR/not_copy.rs:6:12
  |
6 |     value: String,
  |            ^^^^^^ the trait `std::marker::Copy` is not implemented for `std::string::String`
  |
  = help: see issue #48214
  = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable

I would say this lint should be deny by default to keep backwards compatibility.

@eddyb
Copy link
Member

eddyb commented Feb 16, 2020

@Luro02 What else do you generate? Does your proc macro use unsafe and would it do the wrong thing without that "doesn't implement Copy" error?

If so why not put the bound on some impl or fn that would misbehave?

That would actually protect it, unlike an unrelated and unused type definition.

(Alternatively, just use _AssertCopy somewhere in your unsafe code)

@Luro02
Copy link
Contributor

Luro02 commented Feb 16, 2020

@eddyb Nope, in my case it is not using any unsafe code and it would not do the wrong thing, but who knows some macro might use this as a guard. For example one could have an attribute like this:

#[only(copy)]
struct Example {
    field: String, // error because not copy
}

this would silently be accepted.

Alternatively, just use _AssertCopy somewhere in your unsafe code

does this mean, that trivial bounds do not compile in unsafe code? 🤔

The unrelated/unused type definiton is there to get a specific error message (like String does not implement Copy instead of can not move String because it does not implement Copy)

If so why not put the bound on some impl or fn that would misbehave?

I think this would still compile/ignore the bound
fn value(&self) where String: Copy and it is a bit more difficult, than simply emitting a line in the function body.

@dtolnay
Copy link
Member

dtolnay commented Feb 16, 2020

I think the suggestion to "just use the bound somewhere in code" means emitting something like:

struct _AssertCopy where String: ::core::marker::Copy;
let _: _AssertCopy;

There is no way this would compile with an unsatisfied bound.

@eddyb
Copy link
Member

eddyb commented Feb 18, 2020

does this mean, that trivial bounds do not compile in unsafe code? thinking

No, @dtolnay explained what I meant. I was expecting you had unsafe code, but it sounds like you're just using it to get better diagnostics, which makes it less tricky of a situation.

I think this would still compile/ignore the bound

If it doesn't error when compiling the function, it will error when trying to use it.
It's not going to let you call the function without erroring.

@jjpe
Copy link

jjpe commented Feb 18, 2020

Just ran into this issue while implementing a derive macro Delta that derives a trait DeltaOps that:

  1. has an associated type DeltaOps::Delta
  2. has nontrivial bounds on Self
  3. has nontrivial bounds on DeltaOps::Delta
  4. and is implemented for (among others) mutually recursive enums.

That caused an overflow in the compiler, with compile errors like this one:


error[E0275]: overflow evaluating the requirement `rule::Expr: struct_delta_trait::DeltaOps`
  --> src/parser.rs:26:10
   |
26 | #[derive(struct_delta_derive::Delta)]
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: required because of the requirements on the impl of `struct_delta_trait::DeltaOps` for `std::vec::Vec<rule::Expr>`
   = note: required because of the requirements on the impl of `struct_delta_trait::DeltaOps` for `rule::Rule`
   = note: required because of the requirements on the impl of `struct_delta_trait::DeltaOps` for `std::borrow::Cow<'static, rule::Rule>`
   = note: required because of the requirements on the impl of `struct_delta_trait::DeltaOps` for `stack::StackFrame`
   = note: required because of the requirements on the impl of `struct_delta_trait::DeltaOps` for `std::vec::Vec<stack::StackFrame>`
   = note: required because of the requirements on the impl of `struct_delta_trait::DeltaOps` for `stack::Stack`
   = help: see issue #48214
   = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

As you can see, the compiler gave me a suggestion to use #![feature(trivial_bounds)] in my crate.
My question is, will that actually work i.e. will it solve the overflow problem with bounds checking on recursive types without breaking stable functionality?

@cybersoulK
Copy link

ran into this issue when trying to serialize my type with:

#[derive(borsh::BorshSerialize, borsh::BorshDeserialize)]
struct MyType {
   range: RangeInclusive<u32>
   ...
}

@joshlf
Copy link
Contributor

joshlf commented Oct 18, 2023

Chiming in to provide another use case: zerocopy also falls into the category of crates which rely on trivially-false bounds failing in order to guarantee that our derive-emitted code is sound. E.g., all of the derives in this file should fail, but with trivial_bounds enabled, only one of them fails. As long as there's some way for us to fail compilation when a certain type doesn't implement a certain trait, then we're fine. #[deny(trivially_false_bounds)] sounds like it'd work, but from our perspective it doesn't matter if that's the solution or if a different solution is chosen.

One potential concern is that we already have versions of our crate published which don't use #[deny(trivially_false_bounds)], so depending on how this feature is stabilized, it could result in those versions of our crate becoming unsound.

@cramertj
Copy link
Member

@joshlf To clarify, with trivial_bounds enabled, those derives will not trigger a compilation failure, but the resulting unsound trait implementations also won't be present because their bound is false, correct?

@RalfJung
Copy link
Member

RalfJung commented Oct 18, 2023

@joshlf if what you really want is "check if type T implements trait Trait, then I'd express this like this:

fn test_trait(_x: impl Trait) {}
fn test_t_trait(x: T) { test_trait(x) }

It doesn't seem necessary to rely on trivial trait bounds for that?

@joshlf
Copy link
Contributor

joshlf commented Oct 22, 2023

Apologies, it looks like I misunderstood what this feature did: I was incorrectly assuming that trivial bounds were simply removed. I probably should have realized that that behavior makes no sense lol.

In case it's useful, here's the error message that I saw that made me think "oh, if I enable trivial_bounds, this trait impl will start compiling." While that's technically true, I didn't realize it'd compile because the trait would be considered unimplemented. Maybe there's a way to make this more clear in the error message?

error[E0277]: the trait bound `HasPadding<AsBytes2, true>: ShouldBe<false>` is not satisfied
  --> tests/ui-nightly/struct.rs:23:10
   |
23 | #[derive(AsBytes)]
   |          ^^^^^^^ the trait `ShouldBe<false>` is not implemented for `HasPadding<AsBytes2, true>`
   |
   = help: the trait `ShouldBe<true>` is implemented for `HasPadding<AsBytes2, true>`
   = help: see issue #48214
   = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
   = note: this error originates in the derive macro `AsBytes` (in Nightly builds, run with -Z macro-backtrace for more info)

@joshlf
Copy link
Contributor

joshlf commented Oct 22, 2023

I may have found a bug. I changed the PR linked above to use static_assertions::assert_impl_all! to confirm that even if the derive compiles successfully, it doesn't emit an unsound impl. See e.g. this line. However, the assert_impl_all! invocations seem to succeed (see the compiler error output*). It seems that the traits are not actually implemented (see *), but I would expect the assert_impl_all! invocations to fail.

* There is one error in the error output; that's due to the fact that we derive FromBytes, which is a sub-trait of FromZeroes, which is not implemented.

@RalfJung
Copy link
Member

How does assert_impl_all! work?

@joshlf
Copy link
Contributor

joshlf commented Oct 23, 2023

How does assert_impl_all! work?

It calls assert_impl! once for each trait:

macro_rules! assert_impl {
    (for($($generic:tt)*) $ty:ty: $($rest:tt)*) => {
        const _: () = {
            fn assert_impl<$($generic)*>() {
                // Construct an expression using `True`/`False` and their
                // operators, that corresponds to the provided expression.
                let _: $crate::True = $crate::_does_impl!($ty: $($rest)*);
            }
        };
    };
    ($ty:ty: $($rest:tt)*) => {
        // Construct an expression using `True`/`False` and their operators,
        // that corresponds to the provided expression.
        const _: $crate::True = $crate::_does_impl!($ty: $($rest)*);
    };
}

does_impl! is defined here:

/// Returns `True` or `False` depending on whether the given type implements the
/// given trait boolean expression. Can be used in const contexts if it doesn't
/// depend on outer generic parameters.
///
/// This is the core of `assert_impl`.
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! _does_impl {
    ($ty:ty: $($rest:tt)*) => {{
        #[allow(unused_imports)]
        use $crate::{
            _bool::{True, False},
            _core::{marker::PhantomData, ops::Deref},
        };

        // Fallback trait that returns false if the type does not implement a
        // given trait.
        trait DoesntImpl {
            const DOES_IMPL: False = False;
        }
        impl<T: ?Sized> DoesntImpl for T {}

        // Construct an expression using `True`/`False` and their operators,
        // that corresponds to the provided expression.
        *_does_impl!(@boolexpr($ty,) $($rest)*)
    }};

    (@boolexpr($($args:tt)*) ($($expr:tt)*)) => {
        _does_impl!(@boolexpr($($args)*) $($expr)*)
    };
    (@boolexpr($($args:tt)*) !($($expr:tt)*)) => {
        _does_impl!(@boolexpr($($args)*) $($expr)*).not()
    };
    (@boolexpr($($args:tt)*) ($($left:tt)*) | $($right:tt)*) => {{
        let left = _does_impl!(@boolexpr($($args)*) $($left)*);
        let right = _does_impl!(@boolexpr($($args)*) $($right)*);
        left.or(right)
    }};
    (@boolexpr($($args:tt)*) ($($left:tt)*) & $($right:tt)*) => {{
        let left = _does_impl!(@boolexpr($($args)*) $($left)*);
        let right = _does_impl!(@boolexpr($($args)*) $($right)*);
        left.and(right)
    }};
    (@boolexpr($($args:tt)*) !($($left:tt)*) | $($right:tt)*) => {{
        _does_impl!(@boolexpr($($args)*) (!($($left)*)) | $($right)*)
    }};
    (@boolexpr($($args:tt)*) !($($left:tt)*) & $($right:tt)*) => {{
        _does_impl!(@boolexpr($($args)*) (!($($left)*)) & $($right)*)
    }};
    (@boolexpr($($args:tt)*) !$left:ident | $($right:tt)*) => {{
        _does_impl!(@boolexpr($($args)*) !($left) | $($right)*)
    }};
    (@boolexpr($($args:tt)*) !$left:ident & $($right:tt)*) => {{
        _does_impl!(@boolexpr($($args)*) !($left) & $($right)*)
    }};
    (@boolexpr($($args:tt)*) $left:ident | $($right:tt)*) => {
        _does_impl!(@boolexpr($($args)*) ($left) | $($right)*)
    };
    (@boolexpr($($args:tt)*) $left:ident & $($right:tt)*) => {{
        _does_impl!(@boolexpr($($args)*) ($left) & $($right)*)
    }};
    (@boolexpr($($args:tt)*) !$expr:ident) => {
        _does_impl!(@boolexpr($($args)*) !($expr))
    };
    (@boolexpr($($args:tt)*) !$expr:path) => {
        _does_impl!(@boolexpr($($args)*) !($expr))
    };
    (@boolexpr($($args:tt)*) $expr:ident) => {
        _does_impl!(@base($($args)*) $expr)
    };
    (@boolexpr($($args:tt)*) $expr:path) => {
        _does_impl!(@base($($args)*) $expr)
    };

    (@base($ty:ty, $($args:tt)*) $($trait:tt)*) => {{
        // Base case: computes whether `ty` implements `trait`.
        struct Wrapper<T: ?Sized>(PhantomData<T>);

        #[allow(dead_code)]
        impl<T: ?Sized + $($trait)*> Wrapper<T> {
            const DOES_IMPL: True = True;
        }

        // If `$type: $trait`, the `_does_impl` inherent method on `Wrapper`
        // will be called, and return `True`. Otherwise, the trait method will
        // be called, which returns `False`.
        &<Wrapper<$ty>>::DOES_IMPL
    }};
}

@RalfJung
Copy link
Member

That macro looks... interesting:

        const _: () = {
            fn assert_impl<$($generic)*>() {
                // Construct an expression using `True`/`False` and their
                // operators, that corresponds to the provided expression.
                let _: $crate::True = $crate::_does_impl!($ty: $($rest)*);
            }
        };

This is a generic function that is never called. Looks like with this feature we no longer type-check it properly?

I don't know what we guarantee about how much we type-check never-called generic functions.

It would help to distill the behavior you are seeing to a small macro-free example.

@CatsAreFluffy
Copy link

Is the "Chalk" mentioned in the original post related to/the same as the new solver enabled by -Znext-solver? (Also, the example in #48214 (comment) works in the current nightly with -Znext-solver but not without it.)

@matthewjasper
Copy link
Contributor

Yes, I've updated op for that

@cramertj

This comment has been minimized.

@cramertj
Copy link
Member

cramertj commented Dec 20, 2024

#![feature(trivial_bounds)] only required where bounds are... trivial

Maybe this has been obvious to others, but it wasn't to me! The trivial_bounds feature gate and warning are actually only needed in cases where the bound includes no type or lifetime parameters. One can imitate the same behavior by simply adding a type or lifetime parameter to the bound artificially.

That is, this does not compile on stable today:

fn iterate_over_u8(mut x: u8) where u8: Iterator<Item = String> {
    while let Some(s) = (&mut x).next() { println!("{s}") }
}

but this does:

fn iterate_over_u8<'a>(mut x: &'a u8) where &'a u8: Iterator<Item = String> {
    while let Some(s) = (&mut x).next() { println!("{s}") }
}

Both are similarly nonsensical bounds, but the former compiles because the bound isn't evaluated until 'a is substituted at a callsite. This means that my bounds generation example above does not require this feature:

Amended bounds generation example

// BEGIN GENERATED CODE
struct S;

#[diagnostic::on_unimplemented(message = "binding generation of `Clone for S` failed because ...")]
trait CloneForSBindingGenerationFailure {}

impl<'a> Clone for S where &'a (): CloneForSBindingGenerationFailure {
    fn clone(&self) -> Self {
        unreachable!()
    }
}
// END GENERATED CODE

fn main() {
    fn take_clone(_: impl Clone) {}
    take_clone(S); // error[E0277]: binding generation of `Clone for S` failed because ...
}

sunshowers added a commit to oxidecomputer/daft that referenced this issue Mar 10, 2025
Add some span info to try and annotate semantic errors better (such as
highlighting the name of the field that's not diffable).

This isn't perfect for now because we still produce some call-site errors, but
at least we produce one error that's attributed directly to the field name so
rust-analyzer should be happy. I tried adding more bounds but ran into
rust-lang/rust#48214.

Reviewers: andrewjstone

Reviewed By: andrewjstone

Pull Request: #60
@stegaBOB
Copy link

stegaBOB commented Apr 13, 2025

Maybe this has been obvious to others, but it wasn't to me! The trivial_bounds feature gate and warning are actually only needed in cases where the bound includes no type or lifetime parameters. One can imitate the same behavior by simply adding a type or lifetime parameter to the bound artificially.

Wow this was definitely not obvious to me. For all where clause bounds that don't contain any lifetimes it looks like you should be able to emulate this 1:1 by inserting a higher-rank trait bound:

trait Trivial { }

impl Trivial for u8 where String: Copy {} // fails
impl Trivial for u8 where for<'a> String: Copy {} // works fine. Trait just not impld

Edit: Seems like this has been realized already actually haha: #48214 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-RFC-implemented Blocker: Approved by a merged RFC and implemented but not stabilized. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC F-trivial_bounds `#![feature(trivial_bounds)]` S-tracking-impl-incomplete Status: The implementation is incomplete. S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests