-
Notifications
You must be signed in to change notification settings - Fork 14k
Revert "Do not check privacy for RPITIT." #146470
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
base: main
Are you sure you want to change the base?
Revert "Do not check privacy for RPITIT." #146470
Conversation
This reverts commit c004a96.
|
|
|
r? cjgillot |
|
Gentle ping @cjgillot |
|
This came up in today's @rust-lang/lang meeting. It's clear why this needed an FCP (as it's a breaking change), but we didn't feel like we had the context. Could we get a clear ask for what exactly the new hard error is that we're reviewing? Does this just make it a hard error to write a public trait that has something like |
|
It is a little bit more subtle in the current form, the main weirdness I remember is that creating a required method returning a private impl trait does not error out, only providing an implementation does, so does not error while and both error out. The error is also reported when the And then for AFIT it seems to work the same after desugaring, so As I understand it, this is not as strict as it should be based on @petrochenkov's comment and even the first case of defining the trait should be rejected. Here is a playground with more cases to see what does and does not produce errors (though the errors are just comments but compiling the code on this branch should provide the stated results). To summarize, this adds errors when using a private trait in RPITIT but when the offending trait is not used in a trait bound and an implementation is not provided, there is a false negative an the error is not emitted even though it should be. |
|
@bors2 try |
This comment has been minimized.
This comment has been minimized.
…-errors, r=<try> Revert "Do not check privacy for RPITIT."
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
|
@mladedav: I'm having trouble working out the reason why we'd give a hard error for the RPIT-in-trait-impl, trait PrivTr {}
impl PrivTr for () {}
#[expect(private_bounds)]
pub trait PubTr {
fn f1() -> impl PrivTr;
}
impl<T> PubTr for T {
#[expect(private_interfaces)]
fn f1() -> impl PrivTr {}
//~^ error[E0446]: private trait `PrivTr` in public interface
//~| help: can't leak private trait
}given that we don't give an error for an RPIT-in-free-function, trait PrivTr {}
impl PrivTr for () {}
#[expect(private_interfaces)]
pub fn f2() -> impl PrivTr {} //~ OKand given that we allow the comparable associated type desugaring of the RPITIT: trait PrivTr {}
impl PrivTr for () {}
pub trait PubTr {
#[expect(private_bounds)]
type F1: PrivTr; //~ OK
fn f1() -> Self::F1;
}
impl<T> PubTr for T {
type F1 = ();
fn f1() -> Self::F1 {}
}What's the rationale here? I note that on nightly we give an error for this, when desugaring the RPIT-in-trait-impl to ATPIT: #![feature(impl_trait_in_assoc_type)]
trait PrivTr {}
impl PrivTr for () {}
pub trait PubTr {
#[expect(private_bounds)]
type F1: PrivTr; //~ OK
fn f1() -> Self::F1;
}
impl<T> PubTr for T {
type F1 = impl PrivTr;
//~^ error[E0446]: private trait `PrivTr` in public interface
fn f1() -> Self::F1 {}
}What's the rationale here? It makes sense why we can't leak a private type in this way -- we'd then be allowing a private type to be named. Why does this rise to the level of a hard error for a private trait in an impl trait bound? Also, on the PR, I notice that placing the trait PrivTr {}
impl PrivTr for () {}
pub trait PubTr {
#[expect(private_bounds)] //~ warning: this lint expectation is unfulfilled
fn f1() -> impl PrivTr;
//~^ warning: trait `PrivTr` is more private than the item `PubTr::f1::{anon_assoc#0}`
}Should it? |
|
@craterbot check |
|
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
|
🎉 Experiment
Footnotes
|
That's a bug in the implementation of rule #144139 (comment). |
The associated type item newly added for the RPITIT is used as a lint node, which is a different node from |
So, an associated type's interface can contain
type Type<T: PrivTrait /*predicates*/ = PrivTy /*generics*/>: PrivTrait /*bounds*/
where Something: PrivTrait /*predicates*/
= PrivTy /*default type*/;And we just don't differentiate and check all of them when reporting the hard error version of the priv-in-pub check. Why this is a hard error in the first place (rule #144139 (comment))? |
|
Thanks, that makes sense and is helpful. If you could, help me distinguish these cases according to that, or let me know if you think any of these behaviors are incorrect under this model. In particular, I'm suspicious of the hard error in the first case given the other two. A private trait in the bound of an #![feature(impl_trait_in_assoc_type)]
pub trait Super {}
trait PrivTr: Super {}
impl Super for () {}
impl PrivTr for () {}
pub trait PubTr {
type F1: Super;
fn f1() -> Self::F1;
}
impl<T> PubTr for T {
type F1 = impl PrivTr;
//~^ error[E0446]: private trait `PrivTr` in public interface
fn f1() -> Self::F1 {}
}Similar to the above but with the #![feature(impl_trait_in_assoc_type, type_alias_impl_trait)]
pub trait Super {}
trait PrivTr: Super {}
impl Super for () {}
impl PrivTr for () {}
pub trait PubTr {
type F1: Super;
fn f1() -> Self::F1;
}
#[expect(private_interfaces)]
pub type Alias = impl PrivTr;
#[define_opaque(Alias)]
fn defines() -> Alias {}
impl<T> PubTr for T {
type F1 = Alias; //~ OK
fn f1() -> Self::F1 { defines() }
}RPIT-in-free-function: trait PrivTr {}
impl PrivTr for () {}
#[expect(private_interfaces)]
pub fn f2() -> impl PrivTr {} //~ OK |
|
Here type F1 = Alias; //~ OKthe compiler cannot normalize properly, and then see and check the underlying type of So either the normalization should be implemented properly (#126076), or The "RPIT-in-free-function" is ok, it's not an associated type context, so it's a lint and not an error. |
|
Makes sense. Thanks for helping me see the model more clearly. |
|
We talked about this in the lang call today. Let's propose to do this. @rfcbot fcp merge lang |
|
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
We talked about this in the lang call as well. @petrochenkov had explained the reason for this here:
That explains this mechanically, but it still felt like a bug to us. We'd expect that the attribute should take effect when placed above the function item. |
|
We talked about this in the @rust-lang/lang call. I was trying to put this decision on clearer footing. I think the right way to describe our model is that we have
and then we have the notion that nothing in the language should permit other crates to exceed those "capabilities". Useful questions I have found in this space is "if I were am unsafe code, what can I rely on" (e.g., these methods cannot ever be called) and "what kind of changes can I make without affecting consumers of my crate" (e.g., SemVer hazards). I would like a clear statement of "if my module M has visibility onto a given item I, what does that allow me to do, and what can I assume that others cannot do?" One obvious capability is directly naming the item. For a struct, I think we include "accessing the (visible) members of the struct". In other words, a The question then is what are the "capabilities" associated with a trait? Off the top of my head...
Some examples that come to mind. Given this: // Crate A
trait PrivateToMyCrate;
pub trait Get {
type Output;
fn get_output() -> Self::Output;
}
impl Get for () {
type Output = impl PrivateToMyCrate;
fn get_output() -> Self::Output { () }
}then Crate A can add something like this: // also in Crate A:
pub fn foo(t: impl PrivateToMyCrate) { }and then Crate B would be able to do this, which implies that they kinda "know the trait exists" and also "know that it is implemented", albeit only for this opaque type: // in Crate B:
crate_a::foo(<() as Get>::get_output())It's not entirely clear if this is an issue. The idea is that this framework might give us a clearer way to decide issues like this in the future. |
|
@tmandry and I talked about the possibility of trying to draft something like that, either as an update to the RFC or as an issue to FCP, something. |
Yeah, that should certainly be fixable, just a technical issue. |
You can already do something stronger than that today: trait Private {}
impl Private for () {}
#[expect(private_bounds)]
pub fn do_something(_x: impl Private) {}We only lint on this case, and it is easily observable that Furthermore, we allow you to write (unstably, with trait Private {}
pub type Foo = impl Private;I don't see why associated types should be treated any differently than type aliases. I see that @traviscross asked about this before and I think the response from @petrochenkov was:
I don't understand what this is trying to prevent. Put another way, what code does this allow to compile that definitely shouldn't? How is it different from allowing |
|
We could argue, I think, that such code -- the first example -- is OK because we haven't leaked the identity of Perhaps that generalizes. Maybe the relevant question isn't what we've exposed about the private trait but whether we could locally replace that private trait with any other private trait that still offers no less than what we have committed ourselves to with its use in the public item. |
|
Worth noting too is that while we don't give a def-site error for this, trait Priv {}
impl Priv for () {}
#[expect(private_interfaces)]
pub fn f() -> impl Priv {}we do give a use-site error if you try to make that call: mod m1 {
trait Priv {}
impl Priv for () {}
#[expect(private_interfaces)]
pub fn f() -> impl Priv {}
}
fn main() {
let _x = crate::m1::f(); //~ error: trait `Priv` is private
} |
|
In fact, we even give a use-site error just for naming that public item, even if not called: mod m1 {
trait Priv {}
impl Priv for () {}
#[expect(private_interfaces)]
pub fn f() -> impl Priv {}
}
fn main() {
let _x = crate::m1::f; //~ error: trait `Priv` is private
} |
|
Ok, I have spent too much time working out the intent of this change but I think I understand it now. Please let me know if my understanding is correct. I'm filing a concern to check this and also to check that it matches the understanding of lang members who checked their boxes. @rfcbot concern is my understanding correct Lang reportThe intent of this breaking change is to bring the way we treat trait privacy errors in RPITITs into line with RPITs. Specifically, with RPIT it is not possible to call, or even name, a function that returns an RPIT with a private trait from a context that cannot name the trait: (Thanks to @traviscross for the example) mod m1 {
trait Priv {}
impl Priv for () {}
#[expect(private_interfaces)]
pub fn f() -> impl Priv {}
}
fn main() {
let _x = crate::m1::f; //~ error: trait `Priv` is private
}We cannot reproduce this exact behavior with trait methods due to a technical limitation in the compiler. Therefore we pick a more conservative option of erroring at the definition site instead of the use site. This preserves the property that a function cannot be used to produce a value of type If we later want to relax this property we can, but for now this fixes a bug where RPITIT was unintentionally more permissive than other aspects of Rust's privacy system. The breakage is minimal, and isolated to a single crate with a handful of dependencies. |
The changes here were first merged in #143357 and later reverted in #144098 as it introduces new hard errors. There was a crater run tracked in #144139 to see how much projects would be broken (not that many, a few repositories on github are affected).
This reenables hard errors for privacy in RPITIT.
Fixes #143531
Closes #144139
Hopefully closes #71043