Jan 12, 08:00 PM

Other languages have Interfaces; Rust has Traits.1 They are the same thing; each is a set of methods (and perhaps some other constraints) that defines functionality that can be shared by structs or objects or data types or whatever.2 But why “traits”? Why not use familiar terminology? Why shanghai another normal English word into service as jargon? Is Rust3 just being contrarian and different for the sake of being different, like using “crate” instead of “package” or “module”, or Vec instead of ArrayList? Now, I may just not have consumed as much high-quality library code in other languages, but I suspect not. I don’t think the core team was necessarily thinking this when they initially decided on the terminology, but the sense I get is different nonetheless.

Introductory literature in languages like Java or Go tend to explain that an interface is a contract; it’s a set of methods a receiver is guaranteed to take. It is literally the interface, in that it is the way one “uses” the object. Because that’s the way you use an object: by calling its methods. You can get bytes out of an io.Reader data type by calling its .Read() method, etc.

In contrast, the most well-designed Rust crates I’ve encountered define key traits with methods the consumer of the given crate never calls at all. For example, the high-level server library axum exposes the IntoResponse trait and implements it for a bunch of standard library types. When someone using axum writes a request handler, it just needs to return one of these types. You don’t have to think about the .into_response() method at all. (You can, and I have called this method explicitly, but the point is that—and the crate is designed this way—one can make powerful and flexible use of axum without needing to do so.) Or consider the std::future::Future trait, with its lone method .poll(). Unless you’re wrangling a custom future, or writing your own async runtime, you will never explicitly poll a future. You’ll slap an .await on whatever returns it, and the runtime will do all the work for you.

In this sense, the term “trait” really is more appropriate than “interface”. It’s more of an inherent property that a struct has, and it certainly governs now you “use” it,4 but the emphasis of “use” isn’t so much, “call these methods” as it is “an attribute required for this situation”. Which I like.

I’m wide open to being wrong about this. As I mentioned, I suspect I may not have encountered nearly as many well-designed, well-executed libraries in other languages as I have in Rust (not because they’re not there, just because I haven’t looked as hard). I ask those of you with more experience in other languages with interface-style abstractions to think about this and possibly argue with me.

  1. I think Ruby has mixins? They’re the same thing, they just sound tastier. Haskell has Type Classes, which are not quite the same thing, but then again Haskell is a weird beast whose only looping construct is recursion, so we aren’t surprised that it’s different. But my point is that many languages have some sort of support for polymorphism that isn’t based on inheritance, and the term “interface” seems to be the most common, especially in the big-name, heavyweight languages. [return]
  2. Different languages have different ways of referring to their specialized data types that have methods defined on them. Languages big on class-based inheritance tend to call them “objects”; C and some self-consciously “post-OO” languages tend to call them “structs”. I will probably use various terms like “type”, “object”, “struct”, “class” interchangably here. I’m not asserting they’re the same; I’m asserting the common abstraction of “interface” is technicallhy the same. [return]
  3. I say “Rust” here, as if the language has agency and controls its own destiny, but I am of course using the language as synechdoche for those responsible for its development, including the involved portions of the community at large. [return]
  4. Strong type system! Farm! [return]