8

Relax priv-in-pub lint on generic bounds and where clauses of trait impls. by js...

 2 years ago
source link: https://github.com/rust-lang/rust/pull/90586
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The priv-in-pub lint is a legacy mechanism of the compiler, supplanted by a reachability-based type privacy analysis. This PR does not relax type privacy; it only relaxes the lint (as proposed by the type privacy RFC) in the case of trait impls.

Current Behavior

On public trait impls, it's currently an error to have a where bound constraining a private type with a trait:

pub trait Trait {}
pub struct Type {}

struct Priv {}
impl Trait for Priv {}

impl Trait for Type
where
    Priv: Trait // ERROR
{}

...and it's a warning to have have a public type constrained by a private trait:

pub trait Trait {}
pub struct Type {}

pub struct Pub {}
trait Priv {}
impl Priv for Pub {}

impl Trait for Type
where
    Pub: Priv // WARNING
{}

This lint applies to where clauses in other contexts, too; e.g. on free functions:

struct Priv<T>(T);
pub trait Pub {}
impl<T: Pub> Pub for Priv<T> {}

pub fn function<T>()
where
    Priv<T>: Pub // WARNING
{}

These constraints could be relaxed without issue.

New Behavior

This lint is relaxed for where clauses on trait impls, such that it's okay to have a where bound constraining a private type with a trait:

pub trait Trait {}
pub struct Type {}

struct Priv {}
impl Trait for Priv {}

impl Trait for Type
where
    Priv: Trait // OK
{}

...and it's okay to have a public type constrained by a private trait:

pub trait Trait {}
pub struct Type {}

pub struct Pub {}
trait Priv {}
impl Priv for Pub {}

impl Trait for Type
where
    Pub: Priv // OK
{}

Rationale

While the priv-in-pub lint is not essential for soundness, it can help programmers avoid pitfalls that would make their libraries difficult to use by others. For instance, such a lint is useful for free functions; e.g. if a downstream crate tries to call the function in the previous snippet in a generic context:

fn callsite<T>()
where
    Priv<T>: Pub // ERROR: omitting this bound is a compile error, but including it is too
{
    function::<T>()
}

...it cannot do so without repeating function's where bound, which we cannot do because Priv is out-of-scope. A lint for this case is arguably helpful.

However, this same reasoning doesn't hold for trait impls. To call an unconstrained method on a public trait impl with private bounds, you don't need to forward those private bounds, you can forward the public trait:

mod upstream {
    pub trait Trait {
        fn method(&self) {}
    }
    pub struct Type<T>(T);
    
    pub struct Pub<T>(T);
    trait Priv {}
    impl<T: Priv> Priv for Pub<T> {}
    
    impl<T> Trait for Type<T>
    where
        Pub<T>: Priv // WARNING
    {}
}

mod downstream {
    use super::upstream::*;
    
    fn function<T>(value: Type<T>)
    where
        Type<T>: Trait // <- no private deets!
    {
        value.method();
    }
}

This PR only eliminates the lint on trait impls. It leaves it intact for all other contexts, including trait definitions, inherent impls, and function definitions. It doesn't need to exist in those cases either, but I figured I'd first target a case where it's mostly pointless.

Other Notes


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK