Pre-RFC: Renaming OIBITs and changing their declaration syntax

Dear Rustaceans,

So RFC 19 introduced the concept of so-called “OIBIT” traits like Send and Sync. These are traits which are implemented by default for every struct and enum type if that type contains fields whose types all implement the trait. In other words, a struct is automatically Send if all of its fields are Send. Although this RFC has been implemented for some time (thanks @FlaPer87!), most of the details of it remain feature-gated and unstable.

There are two particular details that I would like to discuss changing:

  • the name (currently, de facto, OIBIT);
  • the syntax to declare an OIBIT trait (currently impl SomeTrait for ..).

If you read below you’ll see two suggestions. I sort of like both of them. I’m fishing for feedback on what other people find most intuitive as well as alternative suggestions.

Why change

The acronym “OIBIT”, while quite fun to say, is quite the anachronism. It stand for “opt-in builtin trait”. But in fact, Send and Sync are neither opt-in (rather, they are opt-out) nor builtin (rather, they are defined in the standard library). It seems clear that it should be changed.

The declaration syntax impl SomeTrait for .. also has some clean downsides:

  • It’s not particularly obvious what it means.
  • Whether a trait is an OIBIT trait or not is really a binary property of the trait, but the syntax suggests it could be made conditional.
    • For example, what if I wrote impl<T:Copy> SomeTrait<T> for .. – then is SomeTrait<Vec<i32>> not an OIBIT trait?
      • Answer: at least according to the compiler, it still is.
  • The declaration is quite removed from the trait itself.

What I want to change it to

Well, this is where you come in. I’d like some feedback on the best alternative names. Here are some preliminary thoughts:

Structural trait

Rename OIBIT to “structural trait”.

Change the declaration syntax to structural trait (structural here would be a contextual keyword; in other words, it is only considered a keyword if the word after it is trait).

Why: A “structural” type system is one in which the contents of a type matter and not its name. So, for example, Go’s interface subtyping is structural, because a type Foo implements an interface Bar if Foo defines all of the methods in Bar. It never has to actively declare that it implements Bar. (In contrast, Java interfaces are nominal, because it’s not enough to have methods with the right names, you have to actively declare that you implement the interface).

Why not: Maybe it’s too jargon-y?

Default trait

As above, change the name and syntax to default trait.

Why: the trait is implemented “by default” unless you opt out from it. This is actually (more or less) the name that @FlaPer87 adopted internally within the compiler when he was implementing OIBIT support (we actually call them “defaulted traits”). default is already proposed as a contextual keyword in the specialization RFC.

Why not: seems a bit confusing when you consider the fact that we have a (rather popular) trait named Default. :slight_smile:

FUBAR trait

OK, not a real suggestion, but I totally believe that there are other great keyword choices that will succinctly and (somewhat) clearly express what it going on here. Let your imagination run wild like a horse in a field.

8 Likes

If you want to be really specific, here’s one extreme:

// The ! turns the trait from the opt-in default into opt-out.
unsafe trait !Send {}

// If any field of an ADT doesn't implement Send, then neither
// does the ADT (without this impl, all ADTs would impl Send).
impl !Send for struct {_: !Send, ..} {}

// A specific ADT can opt back in after being automatically opted out.
unsafe impl<T: Unsized> Send for Arc<T> {}
4 Likes

The name should highlight the fact that the trait is auto-derived for structs based on their structure, in this sense "default" is a bad name. In the past many people missed this autoderiving property and erroneously assumed that

trait NonX {}
impl NonX for .. {} // "Default" means blanket?
impl !NonX for X {} 

is roughly equivalent to

impl<T> NonX for T {}
impl !NonX for X {} // Yay, no overlap error, we can emulate specialization!
// ^^^ This even looks like it work for simple cases, except that it breaks when something more complex is involved

"structural" is much better because it sounds mysterious enough to make the user to look it up in the documentation and to figure out what it does exactly and it indeed does something "structural".

I too prefer structural to default. Simply the fact that one would have such a big distinction between “a default trait” and “the Default trait” is enough for me.

Another thought: auto trait, as in “automatically implemented”.

structural, while it sounds like the most accurate here, seems like it may be a bit wordy (not that that’s bad in the long run). Perhaps:

#[structural]
trait Foo {}

// or

inferred trait Foo {} // still seems wordy...

// or

trait Foo { structural; } // they're not allowed to have methods, right?

(just spitballing here)

1 Like

I agree that structural is wordy (though this is a very rare thing to use, so I’m not sure how much it matters, aside from “fit” with the rest of the language).

More possibilities:

  • auto trait Foo; (Edit: whoops, @nikomatsakis already suggested this!)
  • marker trait Foo;
  • kind trait Foo;

(Note that presumably with this syntax we would not even let you use braces, since it has to be empty.)

auto sounds more intuitive than structural.

You could also consider calling this thing something other than a trait, like emotion Send { }.

inherit trait Send {}

So far auto is my favorite I think.

1 Like

chain trait

As in, if one of its members is not a link the chain is broken. You can make types a link, or make them not a link, or you can just leave it to try to chain through the type.

I liked the sound of marker trait Send but not every trait in std::marker is one of these (e.g. Copy isn’t) so it’s probably a bad idea.

kind trait Send is possibly confusing due to higher-kinded types.

+1 for auto trait Send.

auto_trait ?

Yeah, to me a "marker trait" is any trait with no items -- basically, any trait that is used to denote a set of types (or, equivalently, to "mark" when a type has some property).

One thing that can console us is that with OIBIT we did better than the HRE did - as Voltaire said, it was not Holy, nor Roman, nor an Empire, whereas OIBITS are not opt-in, nor built-in, but at least they're still traits. :stuck_out_tongue:

I prefer structural to auto only because auto is used in a really different way in other languages. But then again, if C and C++ have totally different meanings for this keyword, why can't Rust get in on it? Also "structural trait" is easier to say than "automatically implemented trait," and I think we're going to talk about structural traits a lot more than we're going to declare them (between all Rust code-bases I think there might only be the 3, so my post currently beats all Rust code).

I can't see why anyone would want to be able to make a trait OIBIT conditionally, but are we actually sure this wouldn't be a valid use case (that is, maybe the compiler is in error and not the syntax)?

May I propose feeling Send as the declaration syntax, and the unsafe impl to be unsafe Vec<T> is feeling Send where T: Send

2 Likes

I would have pronounced it "automatic trait", I think (or just auto trait).

I'm not aware of a use case, but I guess it's not inconceivable. It seems pretty likely that one could factor into two traits in such a case. It would also be quite a pain to implement. At minimum I guess we'd have to hold off on trying to process any trait reference until enough types are known to evaluate whether it's an OIBIT trait reference or not.

Hi

€0.02: auto is nice but inferred just hits the right synapse :slightly_smiling:

1 Like

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