Skip to content
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

🔬 Tracking issue for RFC 2089: Extended Implied bounds #44491

Open
3 tasks
Tracked by #1
aturon opened this issue Sep 11, 2017 · 33 comments
Open
3 tasks
Tracked by #1

🔬 Tracking issue for RFC 2089: Extended Implied bounds #44491

aturon opened this issue Sep 11, 2017 · 33 comments
Assignees
Labels
B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC S-tracking-impl-incomplete Status: The implementation is incomplete. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@aturon
Copy link
Member

aturon commented Sep 11, 2017

This is a tracking issue for the RFC "Implied bounds" (rust-lang/rfcs#2089).

Steps:

Unresolved questions:

  • Should we try to limit the range of implied bounds to be crate-local (or module-local, etc)?
  • @nikomatsakis pointed here that implied bounds can interact badly with current inference rules.
@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. labels Sep 11, 2017
@nikomatsakis nikomatsakis added WG-traits Working group: Traits, https://internals.rust-lang.org/t/announcing-traits-working-group/6804 E-needs-mentor T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 15, 2017
@TimNN TimNN added the C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC label Sep 17, 2017
@skade
Copy link
Contributor

skade commented Nov 23, 2017

I have a question not covered in the RFC (that not necessarily has to be part of the RFC): what will error reporting on bounds violations look like? Will it point to the original definition of the bound?

@ExpHP
Copy link
Contributor

ExpHP commented Jan 24, 2018

I'm not part of this, but I'll summarize what appears to be the current state of this feature from what I can find:


This tracking issue was posted shortly before the 2017 impl period began, and it looks like it fell under the scope of the WG-compiler-traits workgroup. On the workgroup's dropbox doc I see (under Query model):

We do have to handle cycles at least as well as chalk for implied bounds to work out

I searched the issue tracker for recent issues/PRs about bound cycles or the query model, but nothing stood out.

@L-as
Copy link

L-as commented Jun 10, 2018

Is this ready for being implemented? And if so, could I get some mentoring instructions?

@scalexm
Copy link
Member

scalexm commented Jun 12, 2018

Sorry, it seems like this issue has been remaining silent for a long time!
Basically the current state of implied bounds is as follows:

We would definitely appreciate contributions for this second point, which may eventually enable really cool features in rustc like implied bounds, generic associated types and more generally better type inference. See you on the Zulip stream!

@scottmcm
Copy link
Member

scottmcm commented Jul 31, 2018

I don't know if this is feasible, but if it is, I think it'd be a nice restriction, so wanted to record it for future consideration:

I think this feature is great for direct uses, but weird for "sideways" uses. To try a concrete example:

// in a world where `struct HashSet<T: Hash+Eq>`...
fn foo<T>(s: &mut HashSet<T>, a: T, b: T) -> bool {
    let r = a == b; // not allowed, because _this_ method doesn't know T: PartialEq
    s.insert(a); // allowed, because the method's `Self` type is HashSet, where we always know Hash+eq
    r
}

I think that would keep the core "look, I'm calling a hashset method, so obviously it's Hash+Eq; I shouldn't need to say so again" but still wouldn't let you directly call things on traits that you never mentioned.

(I do, however, think that in that world, impl<T> HashSet<T> or impl<T> Foo for HashSet<T> should allow use of T:Hash+Eq in the whole impl block, since again it's core to the "self type".)

bors added a commit that referenced this issue Dec 27, 2018
Implement the new-style trait solver

Final PR of what I believe to be a minimally working implementation of the new-style trait solver.

The new trait solver can be used by providing the `-Z chalk` command line flag. It is currently used everywhere in `rustc_typeck`, and for everything relying on `rustc::infer::canonical::query_response::enter_canonical_trait_query`.

The trait solver is invoked in rustc by using the `evaluate_goal` canonical query. This is not optimal because each call to `evaluate_goal` creates a new `chalk_engine::Forest`, hence rustc cannot use answers to intermediate goals produced by the root goal. We'll need to change that but I guess that's ok for now.

Some next steps, I think, are:
* handle region constraints: region constraints are computed but are completely ignored for now, I think we may need additional support from `chalk_engine` (as a side effect, types or trait references with outlive requirements cannot be proved well-formed)
* deactivate eager normalization in the presence of `-Z chalk` in order to leverage the lazy normalization strategy of the new-style trait solver
* add the remaining built-in impls (only `Sized` is supported currently)
* transition the compiler to using generic goals instead of predicates that still refer to named type parameters etc

I added a few very simple tests to check that the new solver has the right behavior, they won't be needed anymore once it is mature enough. Additionally it shows off that we get [implied bounds](#44491) for free.

r? @nikomatsakis
@alexreg
Copy link
Contributor

alexreg commented Jan 1, 2019

Is anyone working on implementing this RFC yet? @scalexm, maybe?

@scalexm
Copy link
Member

scalexm commented Jan 1, 2019

Chalk already handles implied bounds. I don’t know of any plan for implied bounds in Rust outside of pushing on chalk integration.

@alexreg
Copy link
Contributor

alexreg commented Jan 2, 2019

@scalexm Oh right. So once Chalk is fully integrated, they'll just "start working" eh? In that case, I'd love to help with Chalk and speed things along... though I feel I'm a bit overstretched and underqualified as it is.

@scalexm
Copy link
Member

scalexm commented Jan 2, 2019

Note that they "work" if you enable -Z chalk:

https://github.com/rust-lang/rust/blob/81d6f9cc813e8946b6cb2ee29dffeb0000c63e69/src/test/run-pass/chalkify/type_implied_bound.rs

But since you cannot write any useful code with -Z chalk right now (types with lifetime requirements cannot be proved to be well-formed, which is prohibitive), it is not really useful at the moment lol

jonhoo added a commit to GarkGarcia/flurry that referenced this issue Jan 30, 2020
This is necessary since there is no particular lock implementation that
"makes sense" on `no_std`. This patch changes all types to be generic
over any lock type that implements `lock_api::RawMutex`, and defaults to
`parking_lot::RawMutex` on `std`.

Note that this patch forks the struct definition for `HashMap` into one
for `std` (which has a default for `L`) and one for `no_std` (which does
not). This is fine since any code written _without_ the `std` feature
_will_ compile with the `std` feature enabled (`indexmap` does the same;
the reverse is not true). We _could_ introduce an intermediate private
struct to avoid repeating the definition, but it would require writing
`self.inner` all over the place, which would be sad. This seemed
marginally cleaner.

Also, this diff makes me want implied bounds a lot:
rust-lang/rust#44491
@scottmcm
Copy link
Member

especially as there are still types like std::borrow::Cow which have no choice but to place the bounds on the type

This makes me wonder about a potentially-much-simpler version of this: could we say that a function doesn't need to "prove" the bounds needed for types it mentions in its signature?

So, for example, I could write this:

fn is_borrowed<T>(x: Cow<T>) -> bool {
    if let Cow::Borrowed(_) = x { true }
    else { false }
}

And that would be fine, because I'm not doing anything that needs the bound.

What would need the bound? Any constructor (struct literals, variant functions, etc), and any call to a function (like Cow::to_mut) that itself requires the bound (perhaps via a trait impl requiring the bound).

Do you think that would be sound? It feels like it ought to be -- though I'm nowhere near sophisticated enough to prove it -- as the cases that don't meet the bounds would be rather like the trivial_bounds cases of where String: Copy that we already deal with.

@dhardy
Copy link
Contributor

dhardy commented Apr 5, 2022

For those looking for an alternative that works today using macros, try impl-tools:

impl_scope! {
    struct Foo<T: Debug> { .. }

    // expands to impl<T: Debug> Self { .. }
    impl Self { .. }

    // where bounds supported too:
    impl Self where T: PartialEq { .. }
}

Obviously this has the same drawback as mentioned above: stronger bounds on the type than are required just to construct the type. I haven't found this an issue in my own usage, but appreciate that it could be in some cases.

@lcnr lcnr added T-types Relevant to the types team, which will review and decide on the PR/issue. S-types-tracked Status: Being actively tracked by the types team and removed WG-traits Working group: Traits, https://internals.rust-lang.org/t/announcing-traits-working-group/6804 S-types-tracked Status: Being actively tracked by the types team labels Jul 15, 2022
@nikomatsakis nikomatsakis changed the title 🔬 Tracking issue for RFC 2089: Implied bounds 🔬 Tracking issue for RFC 2089: Extended Implied bounds Nov 30, 2022
@daniil-berg
Copy link

daniil-berg commented Nov 25, 2023

@Andlon Have you or has someone else here been able to find some kind of workaround/hack for this problem you described, while this issue is still open?

One rather big pain point in generic numerical code is the fact that it's very hard/impossible to ergonomically express the fact that we want to be able to do arithmetic operations not just on T, but on &T and all combinations thereof. [...]

I came across this problem and was pulling my hair out, until I found out that this is a "shortcoming" of the Rust compiler. I agree that it is absolutely not feasible to repeat those redundant trait bounds over and over everywhere the trait itself is used.

Even the following MRE (playground) is enough to stump the compiler:

use std::ops::Add;

trait RefAddable: Sized
where
    for<'a> &'a Self: Add<Output = Self>,
{}

fn do_stuff<T: RefAddable>(x: &T) {}

Causes the following error:

fn do_stuff<T: RefAddable>(x: &T) {}
               ^^^^^^^^^^ no implementation for `&'a T + &'a T`

Telling me I need to add the bound again:

fn do_stuff<T: RefAddable>(x: &T) where for<'a> &'a T: Add {}
                                  ++++++++++++++++++++++++

Can this be worked around with a proc macro somehow?
Or is there some hacky magic with marker traits possible here?

PS

I just found out about trait aliases. So at least in nightly it seems you could work around this with a trait alias:

#![feature(trait_alias)]

use std::ops::Add;

trait RefAddable = where for<'a> &'a Self: Sized + Add<Output = Self>;

fn do_stuff<T: RefAddable>(x: &T, y: &T) -> T {
    x + y
}

(playground with test)

Seems good enough at first glance. But I am still curious, if someone found another workaround.

@djc
Copy link
Contributor

djc commented Apr 11, 2024

Does anyone have a sense of what is blocking this at this point? Surely would be nice to have...

@rsalmei
Copy link

rsalmei commented Jun 13, 2024

Rust 1.79 has just stabilized implied bounds ability on Supertraits! (Full ability I mean, including Associated Type bounds)
Hopefully, we are a bit closer to full implied bounds now.

ids1024 added a commit to pop-os/cosmic-comp that referenced this issue Aug 5, 2024
It would make sense to have a bound like
`for<'frame> R::Frame<'frame>: AsGlowFrame<'frame>`. But that appears to
not behave properly due to current limitations of the borrow checker:
https://blog.rust-lang.org/2022/10/28/gats-stabilization.html#implied-static-requirement-from-higher-ranked-trait-bounds

Instead, this makes `glow_frame` and `glow_frame_mut` associated
functions of the `AsGlowRenderer` trait. Then it is pretty
straightforward to make the `RenderElement` implementations generic
using that and `FromGlesError`.

It would make sense to make `Self::Error: FromGlessError` a requirement
of the `AsGlowRenderer` trait, but due to the lack of implied bounds
support, that produces a bunch of errors about missing bounds. If Rustc
improves that eventually, some bounds could be cleaned up a bit:
rust-lang/rust#44491
Drakulix pushed a commit to pop-os/cosmic-comp that referenced this issue Aug 5, 2024
It would make sense to have a bound like
`for<'frame> R::Frame<'frame>: AsGlowFrame<'frame>`. But that appears to
not behave properly due to current limitations of the borrow checker:
https://blog.rust-lang.org/2022/10/28/gats-stabilization.html#implied-static-requirement-from-higher-ranked-trait-bounds

Instead, this makes `glow_frame` and `glow_frame_mut` associated
functions of the `AsGlowRenderer` trait. Then it is pretty
straightforward to make the `RenderElement` implementations generic
using that and `FromGlesError`.

It would make sense to make `Self::Error: FromGlessError` a requirement
of the `AsGlowRenderer` trait, but due to the lack of implied bounds
support, that produces a bunch of errors about missing bounds. If Rustc
improves that eventually, some bounds could be cleaned up a bit:
rust-lang/rust#44491
@gtsiam
Copy link

gtsiam commented Nov 24, 2024

@daniil-berg I present to thee, my Evil Trait Hack™! (playground)

EDIT: Now in crate form.

use std::ops::Add;

trait RefAddable: Sized
where
    // for<'a> &'a Self: Add<Output = Self>,
    Self: for<'a> Imply<&'a Self, Is: Add<Output = Self>>,
{}

fn do_stuff<T: RefAddable>() {}

// Evil trait hack

trait Imply<T>: ImplyHack<T, Is = T> {}

impl<T, U> Imply<T> for U {}

trait ImplyHack<T> {
    type Is;
}

impl<T, U> ImplyHack<T> for U {
    type Is = T;
}

@gtsiam
Copy link

gtsiam commented Nov 24, 2024

... which of course just a few moments after posting, I realize doesn't quite work. Naturally.

Fixed.

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. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC S-tracking-impl-incomplete Status: The implementation is incomplete. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests