Why does rust use struct keyword?


#1

Hello Everyone,

Why does rust use struct keyword when in fact structs in rust act as class more than structs in C languages? It could have been named class or protocol like in swift or something unique. struct name is great as well but it might confuse other programmers since it’s always copied by value completely in other programming languages while it’s ‘ownership’ copied in rust.

Thanks,


#2

it’s always copied by value in other programming languages.

This is also true in Rust?

Why does rust use struct keyword when in fact structs in rust act as class more than structs in C languages?

I don’t see how this is true?


#3

Yes, ownership copy… To have a true copy you need to implement Copy trait.


#4

Yes, you are correct here. struct in rust is special nevertheless. Thanks for clarification.


#5

Because they don’t act like classes in OO languages, and they are much closer to plain old C structs. They don’t provide inheritance, for example. They are value types, unlike classes in languages exactly like Swift where it’s mandatory to access class instances through a pointer only. And so on.

protocol is a completely different concept. (It’s closer to traits in Rust.) It has almost nothing to do with structs or classes.

You mean they are moved? Moving vs. implicit copying is the property of every non-Copy type in Rust, not just structs. It’s an issue completely orthogonal to values being of struct or other types.


#6

I was giving an example of a name, I didn’t say it needs to be named class or protocol. It can be named anything else. As you know, rust is unique in its approach to programming and I thought having unique keywords to signify this uniqueness could be a good idea. Nevertheless, after Manishearth reply, I got a clear idea of how structs work in rust, and I think struct is a great keyword as well and see why it’s named this way.


#7

I don’t understand what you’re saying here.

Yes, non-Copy structs are moved, but that’s still a copy at the machine level, not a reference.

Rust’s affine types are unique amongst the languages you mention so I fail to see how it’s relevant.

(Also, C++ classes are copyable too)


#8

Yes, I agree, that wasn’t clear to me at the beginning of the post. Thanks for clarification.


#9

It depends how unique. Niko had a good post about this recently.

Note that if you try to use struct (and enum) in Rust like you would in C, it works pretty much the same. They may have additional things they can do, and there are always slight nuances (like opt-in Copy), but they’re used in roughly the same ways for roughly the same things, so the intuitions are valuable.

Compare trait, which could have been named interface, but wasn’t. One could argue either way, but I think overall that was the right choice, since even though they have similar goals if you squint hard enough, trying to take a Java-style design with interfaces and translate it directly into Rust has a whole bunch of gotchas.


#10

Personally, I would have preferred just one concept data as in ML like languages such as Haskell.

Examples:

data Foo;
data Foo();
data Foo(usize, usize);
data Foo { foo: T, bar: U }
data Foo { V1(String, bool), Green, Msg { id: usize, message: String }
data Void {}

@scottmcm I think it was a mistake to call type classes trait that work nothing like mixins in PHP and such. The keyword interface speaks much more about the role of type classes even if Java interfaces have some limitations wrt. return type polymorphism, no associated types and consts, etc… In fact, Idris, calls their type classes interface. Unfortunately, C++, Java and similar languages use class for struct + OOP features, so it would have been confusing to call trait class instead, even if it would had stayed true to Haskell and the notion of type classes.


#11

The idea behind traits has always been for them to serve the role of a mixin, like in PHP or Scala. You can see this in traits like Iterator, which are essentially libraries of provided methods that types can incorporate by implementing a small number of required methods. This distinction is one of the key reasons why “trait” was chosen over “interface”, though the distinction is no longer as useful since Java interfaces can now have default methods. Yes, the design has branched off significantly from that—there’s the focus on parametric polymorphism over subtype polymorphism, having methods namespaced by their trait rather than having a conflict resolution protocol, and various other differences—but I definitely wouldn’t claim that Rust traits have nothing to do with mixins. Beyond this, the name “trait” is doubly appropriate because marker traits like Copy, Send, Sync actually do represent the properties (i.e. traits) of a type rather than their interface.

I think going with struct and enum was the right decision. Yes, they correspond to data types in more functional languages, but part of the purpose of Rust is to bridge the gap back to imperative languages, and using terminology familiar from imperative languages for something basic like a struct really helps with that. It’s why a lot of the basic syntax (curly braces, field access, function call syntax) derive from the C family despite the language initially being heavily inspired by ML and then Haskell.


#12

Is that true? If so, where is this historical fact documented? This seems strange to me, given that traits are functionally equivalent to type classes in Haskell with the head parameter implicit and special syntax for dot notation. Haskell type classes are for sure not used like mixins. Let’s not forget that Rust traits are not like Scala traits (so the association with Scala leads away from understanding IMO).

The fact that Iterator has a bunch of extra derived methods seems to me nothing more than:

  1. a way to take advantage of dot syntax, 2) to gain in efficiency for some cases by allowing the user to provide more specialized definitions for derived operations.

Point 2) also applies to Haskell where you can give minimal complete definitions such as with the (>>) (blind) operator which is derived.

Point 1) is just a technical detail to me given that you could also allow all free functions with arity of 1+ to be called like x.f(y), and allow for infix chaining, instead of f(x, y). So here the call syntax of Haskell and Rust differs, but this is only a matter of ergonomics of composition. Haskell achieves superior compositionality with partial application (or “auto currying”) and function composition.

Fundamentally however; traits are still not like mixins because you usually have primitive operations which all derived operations of the trait are based on. (PHP does have a notion of abstract trait members however)

You make a good point about trait being appropriate as hinting at “constraint”. Speaking of constraints, I would probably have chosen constraint as the keyword and removed the head parameter. But of course, this is all contrafactual reasoning. We chose trait and we are sticking with it. All of this discussion is just for fun and posterity. :wink: I do think class would have been even more optimal if we could forget about classes in languages with the usual subtype polymorphism. Haskell is really a beautifully designed language. The notion of a “class of types” is really neat.


I get the usual complexity budget argument. I also use that reasoning from time to time. Recently, I used that argument for try { .. } over catch { .. }. In the case of data vs. struct and enum, I think data is IMO sufficiently clear that it wouldn’t have consumed too much of the budget.


#13

I’ve been digging around. So far, this is what I’ve found, but I remember there being more discussion about it:

https://mail.mozilla.org/pipermail/rust-dev/2012-August/002276.html

Note that the change to trait from iface coincides with wanting default implementations. There was a lot of inspiration from Haskell typeclasses at the time, too, which I’ve found in various blog posts before and after the change was made.


#14

Ah yes, interesting! I did watch https://air.mozilla.org/rust-typeclasses/ a while back.

I think things changed a lot from then. Aside from object safety (in Haskell all type classes are “object safe”) and some minor syntactic differences, I think it is too weak a statement to say that traits are inspired by type classes. I’d say that traits are type classes. Associated types and GATs make this even more clear.


#15

This is what distinguishes traits from vanilla mixins in general. The whole idea is to have a set of provided methods based on a set of required methods. Yes, Rust’s traits can be understood as typeclasses with implicit head and dot notation, and this has been understood all the way along. However, the choice to have implicit head and dot notation wasn’t incidental as you are suggesting. There was a deliberate decision to frame Rust traits using object-oriented rather than functional concepts, which is why they’re called “traits” and have “methods”—notionally belonging to the implicit head parameter—which can be accessed with dot notation.

When I said “inspired by typeclasses”, I meant how they are implemented in Haskell. Aside from being framed in terms of methods, traits differ from Haskell typeclasses in how coherence is handled. Traits aren’t just a transcription of Haskell typeclasses with some quirks; every step of the way, design decisions were being made about whether to have named instances, how coherence should work, etc… consulting Haskell typeclasses as a reference but not limited to making the same decisions as Haskell.


#16

For sure it was not incidental. But I do think the implicit head was a mistake in hindsight. It is creating problems for HKTs designs and modelling nullary type classes (sure, the latter is a ridiculously obscure concept that more or less no one uses in Haskell).

I don’t doubt that traits were framed in OO terms, but to say that the goal of traits were to serve as mixins does not necessarily follow from that. As an aside, in Haskell, type class functions are referred to as methods, so there is not much difference here.

I don’t doubt that design decisions were made in every step of the way - I expect as much, given the different memory model of Rust; but to me, the end result became Haskell type classes with some minor differences (dot syntax, implicit head, object safety, specific rules around coherence).


#17

That’s just a question of syntax really. You can model them on ATCs, which incidentally ensures the whole thing plays well with coherence. If you have trait Collection<T> for C<T> or something, this can map to having an associated type C: CollectionFamily where C::Apply<T> = Self. CollectionFamily::Apply is an ATC. Rename CollectionFamily to just Collection, and C::Apply<T> to C<T>, and you have HKTs. It’s just a question of what you want the syntax for creating the associated type C with the above properties to be. As long as you can only implement Collection<T> and not Collection (aka CollectionFamily), it plays well with coherence and inference.

Whatever the drawbacks of implicit head, it does serve the purpose of making the feature much more approachable for people coming from an OO background, and I think that’s pretty huge as far as facilitating adoption.

Unless you’re arguing for some sort of death-of-the-author interpretation, we don’t have to reconstruct motivations from present circumstances. Traits were introduced as an incremental change from interfaces that added default methods for the purposes of enabling method implementation reuse based on the trait flavour of mixins, and they still serve this purpose.

I mean methods in the OO sense of being member functions of the head type as opposed to free functions.

I don’t understand why you’re arguing this. Nobody is denying that traits map to typeclasses, but they’re also designed to be and are traits in the OO sense. It happens that these two ideas converge in such a way that Rust traits can represent a duality between them.


#18

Sure; The problems it creates are subpar syntax =)

This is fair; but I think it is overstated. You can still have fn foo(self: Head). You can even assume self: Head when fn foo(self) is typed.

Are mixins the main use case tho today? Even Iterator does not act as a mixin due to the associated type Item.

Even tho you invoke them as x.f(y, ..), they aren’t really member functions of the head type, and having to use Trait; shows this IMO. One further large difference that hints at this is that unlike traits in Scala and PHP is that you don’t extend a type with a trait like so:

impl MyType extend MyTrait {
    // trait functions..
    // other methods..
}

Not having subtype polymorphism seems like a crucial distinction that at least to me, makes traits not traits in the OO sense. I suppose dyn Trait muddles this, but then again, Haskell has existential quantification of type classes as well.


#19

Iterator absolutely acts as a mixin. You provide a small amount of implementation and suddenly you have a bunch more methods provided automatically. The tweak “you must provide a type as well as a function” does not disqualify it- PHP doesn’t even have static types to compare to!


#20

They’d be even less useful in Rust unless you updated the coherence rules, since otherwise the impl would have to be in the same crate as the declaration, in which case it degenerates to a module. I suppose you could modify the rules here so that Ti is the first local type in [ T0...Tn, TERM ], where TERM is considered LT exclusively in executable crates.

Needing use Trait is just namespacing complementary to type membership :wink:. Sure, this is spin, but so is framing traits in such a way that ignores the privileging of the head type. As I said, traits represent a duality; they were designed with a foot in both worlds. The head type “really” is privileged by the language, specifically so you can treat trait methods as members of the head type. Framing things so that this isn’t “real” is arguing a counterfactual.

That you can write code that is polymorphic over traits is more important than the exact syntax, afaic. But yes, there is a reason why dyn Trait has been referred to as an “object type”.