Making build-in traits less magical

Custom derives already exist in the form of procedural macros. And that mechanism is far more general then your proposal would allow, I'm not even sure how it would be used to derive any interesting non-marker traits, since reflection is basically non-existent in the trait system โ€” how would Struct allow getting the value of a field, or its name, generically?

My memory may be faulty, but what are these multiple ad-hoc mechanisms, besides auto traits (impl Tr for ..) and Copy? Leaving aside the questions of whether this proposal can even model Copy (negative bounds seem required for that), replacing auto traits with something more complex only to capture Copy (which needs special compiler support in many other ways, too) seems like a bad deal.

I would also like to point out that auto traits are using different rules from normal traits, specifically they are using coinductive reasoning instead of inductive reasoning. This means (a sound kind of) "infinite regress" is permitted when testing if a type implements an auto trait. For a type such as

struct List<T> { data: T, next: Option<Box<List<T>>> }

this means that it's Send, but a trait Send2 written in the obvious way with your proposal would cause infinite recursion: to check if List<T> : Send2, we must check whether all fields are Send2, which will lead us to testing List<T> : Send2 again. Permitting coinductive reasoning for "normal" traits is unsound in general, so we can't just change that. We'd need another new feature that allows certain traits to opt into coinductive reasoning, but this:

  • Will come with certain restrictions on the trait (it must be a pure marker trait, for starters)
  • Will imply restrictions on what you can do with the trait (you can't always "mix" inductive and coinductive reasoning and expect the result to be correct)
  • And thus will keep around most of the complexity of auto traits.

You may still argue that this is a better state of affairs because it's more orthogonal or something, but at the very least the proposal as currently stated isn't powerful enough.

There are not specific rules for each primitive trait; there are two different cases in which traits are implemented automatically: auto trait (Send, Sync, UnwindSafe) & Sized. You propose three new special cases. This does not seem simplifying on the face of it.

This change is not well-motivated enough for us to devote attention to it over the many other things we want to do. This would be a deep change to our core language for an ambiguous benefit. I don't think its fruitful to pursue it further (except as an academic exercise, which could be edifying).

Custom derives already exist in the form of procedural macros. And that mechanism is far more general then your proposal would allow, I'm not even sure how it would be used to derive any interesting non-marker traits, since reflection is basically non-existent in the trait system โ€” how would Struct allow getting the value of a field, or its name, generically?

@hanna-kruppe Maybe you have missed those lines (with type Tuple similar to what is proposed in 1935):

In some aspect my proposal is not that far from a sort of reflection. Basically a few primitive auto implemented traits that would allow some kind of internal inspection of every type in Rust.

My memory may be faulty, but what are these multiple ad-hoc mechanisms, besides auto traits (impl Tr for ..) and Copy.

As far as I know logic for derived implementation of Ord, Eq, Clone, Copy and the likes was directly implemented inside the compiler. But that may have changed. Can anyone confirm or disconfirm?

Leaving aside the questions of whether this proposal can even model Copy (negative bounds seem required for that), [...]

I've never denied it would need negative bounds. This proposal is strictly prospective and does need other feature to be available to work efficiently.

I would also like to point out that auto traits are using different rules from normal traits, specifically they are using coinductive reasoning instead of inductive reasoning.

This argument looks interesting and I'm completely ready to believe you but to be honest I'm not sure I understand it very well. If I were to implement manually any trait on your type - for example providing some impl PartialEq for List<T> where T : PartialEq - with the obvious field-based implementation, the compiler would need to check if this implementation is legal which looks from my (certainly naive) point of view similar to the infinit regress you're talking about (the implementation of PartialEq for List<T> is valid if there exist a valid implementation of PartialEq for List<T>). What makes those two cases different? (This is genuine question, not a rethorical one)

There are not specific rules for each primitive trait; there are two different cases in which traits are implemented automatically: auto trait (Send, Sync, UnwindSafe) & Sized.

Copy is also a special trait. To be clear again this discussion concerns more than auto traits.

You propose three new special cases. This does not seem simplifying on the face of it.

@withoutboats If you are counting traits the fact is I propose more than 3 "special cases". I focused on Tuple, Struct and a bit on Enum but you could also imagine a trait Closure (The nice thing on this one is that it would allow access to the inner state of closures, that are as far as I know inaccessible). But in the end all those traits would have the same purpose: inspecting the types content by projecting them on tuples (so in the end I consider this family of traits just one single special case. Different perspective I guess).

This change is not well-motivated enough for us to devote attention to it over the many other things we want to do. This would be a deep change to our core language for an ambiguous benefit. I don't think its fruitful to pursue it further (except as an academic exercise, which could be edifying).

I have a real problem of tone here. I have the impression that I'm opposed a sort of brutal condescending refusal: "Our time is too precious for sterile proposals like these. Get out and play alone if you have time to lose". To be clear I'm not forcing anyone to implement this. I'm not even proposing a pre-RFC. This was just an invitation to explore some ideas. I would have expected more a constructive answer.

I indeed missed them, thanks.

It worries me that all these different (one might even say unrelated) features are proposed together, "just" to solve the problem of special traits. Adding static reflection and negative bounds and coinductive traits (and possibly more) is some really heavy machinery. Of course, there would be many further uses for most of them, but if that is the motivation, then that should be the focus, not Copy and auto traits. Even then, I would like to evaluate each feature on its own merits (keeping synergies with other features in mind, but neatly separating them). If these features only make sense when considered together, they may not be as orthogonal as they appear.

Ah, yes, I forgot about this too. Sorry.

I think it's in the compiler source code, but that's for historical reasons. All the derives can go through proc macros nowadays.

Most impls won't be recursive in the same way โ€” crudely put, the recursion is not "in where clauses". With PartialEq, there's a recursive call is in the body of the implementation of PartialEq::partial_eq. Type checking this recursive call requires assuming List<T>: PartialEq, but whatever result you get from type checking the body can only influence whether the function is valid, not other facts about which traits are implemented for what types. So it can't lead you to wrong conclusions about whether a type implements an unrelated trait. (Edit: here's one issue where this was a problem Cyclic traits allow arbitrary traits to be synthesized ยท Issue #29859 ยท rust-lang/rust ยท GitHub)

It worries me that all these different (one might even say unrelated) features are proposed together, "just" to solve the problem of special traits. Adding static reflection and negative bounds and coinductive traits (and possibly more) is some really heavy machinery.

@hanna-kruppe I was not proposing to add negative bounds, just supposing that they would be eventually integrated in the language. To be honest I cannot find what is the last status on that topic. I know some fundamental issues were found that prevented an earlier integration in the language (just like specialization). Are they a dead proposal or are deeper explorations planned? Concerning coinductive traits my knowledge in this domain is too limited.

Of course, there would be many further uses for most of them, but if that is the motivation, then that should be the focus, not Copy and auto traits.

I initially considered Copy, auto-traits and derived traits were a sufficiently strong motivation. Now it appears people consider either good alternative features have already been added or that the status quo is sufficient. As you said a static reflection system may offer other interesting perspectives. Accessing the content of closures seems one of them.

Even then, I would like to evaluate each feature on its own merits (keeping synergies with other features in mind, but neatly separating them). If these features only make sense when considered together, they may not be as orthogonal as they appear.

The only feature I'm really trying to investigate is this kind of static reflection. I think my OP was published as an invitation to investigate and share opinions on the idea rather to impose some sort of conceptual model. Have anyone else expressed specific needs that may be filled by this very kind of reflection system?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.