Currently the derive macros for std
traits place a needless bound on generic arguments in their implementation. For example, deriving Default
on this struct will expand to the following
#[derive(Default)]
pub struct Foo<T> {
value: Option<T>,
}
impl<T: Default> Default for Foo<T> {
#[inline]
fn default() -> Foo<T> {
Foo {
value: Default::default(),
}
}
}
However, this is overly limiting, because Option<T>
satisfies default for any T
, not just T: Default
. This makes code like this not compile, even though it obviously should:
// error[E0277]: the trait bound `T: Default` is not satisfied
fn new_foo<T>() -> Foo<T> {
Foo::default()
// ^^^^^^^^^^^^ the trait `Default` is not implemented for `T`
}
The problem is easy to fix, we want the field types to satisfy the trait, not the generic arguments. Thus, the macro should actually expand to:
impl<T> Default for Foo<T>
where
Option<T>: Default,
{
#[inline]
fn default() -> Foo<T> {
Foo {
value: Default::default(),
}
}
}
All std
derive traits have this problem. I've frequently needed to write my own implementation of trivially derivable types because of this restriction. Even though generic arguments are often field types, this should not be assumed to be always the case, and limiting the implementation like this diminishes the usefulness of the macros.
One issue with this is privacy
#[derive(Default)]
struct PrivateType<T>(T);
#[derive(Default)]
pub struct PublicType<T>(PrivateType<T>);
// expands to
impl<T> Default for PublicType<T> where PrivateType<T>: Default { ... }
You cannot mention private types in the where
clauses of public APIs (despite auto-traits essentially doing so...).
kpreid
August 2, 2022, 9:59pm
3
And it would be a semver hazard if adding a field to a type changed its trait bounds. (This too is already the case for auto traits, unfortunately.)
While I do get what you mean with this (and if it is in fact so it is a deal breaker), this particular example seems to compile fine in the playground .
Well, to be fair, the trait bounds are already implied, this would just state them in the implementation. It would not work any differently as the way it works now.
kpreid
August 2, 2022, 10:15pm
6
Right now, all the trait bounds are derived from always-public information about the type: what its generic parameters are, and not from potentially private information, what the types of its fields are.
2 Likes
Interesting, that changed recently
rustc 1.58.1 (db9d1b20b 2022-01-20)
error[E0446]: private type `PrivateType<T>` in public interface
--> main.rs:6:1
|
2 | struct PrivateType<T>(Option<T>);
| --------------------------------- `PrivateType<T>` declared as private
...
6 | / impl<T> Default for PublicType<T>
7 | | where
8 | | PrivateType<T>: Default,
9 | | {
... |
12 | | }
13 | | }
| |_^ can't leak private type
error: aborting due to previous error
For more information about this error, try `rustc --explain E0446`.
rustc 1.59.0 (9d1b2106e 2022-02-23)
Compiled successfully
rust-lang:master
← jswrenn:relax-privacy-lints
opened 08:44PM - 04 Nov 21 UTC
The priv-in-pub lint is a legacy mechanism of the compiler, supplanted by a reac… hability-based [type privacy](https://github.com/rust-lang/rfcs/blob/master/text/2145-type-privacy.md) 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:
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
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
- See discussion [on zulip](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/relax.20priv-in-pub.20lint.20for.20trait.20impl.20.60where.60.20bounds/near/222458397).
- This PR effectively reverts #79291.
This is a very old, well known issue:
opened 09:58PM - 09 Jul 15 UTC
A-traits
P-low
T-lang
C-feature-request
A-proc-macros
In the following code:
```
#[derive(Copy, Clone)]
struct Y<T>(&'static fn(T));
… ```
both derives expand to impls that require the corresponding trait to be implemented on the type parameter, e.g.:
```
#[automatically_derived]
impl <T: ::std::marker::Copy> ::std::marker::Copy for Y<T> where
T: ::std::marker::Copy {
}
```
However, this isn't actually necessary, as `Y<T>` will still be eligible for `Copy` regardless of whether `T` is.
This may be hard to fix, given the compilation phase at which `#[derive]` works...
3 Likes
system
Closed
November 1, 2022, 1:47am
10
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.