I remember writing Rust in university a year before this was published. How far the language has come since then! The title should be updated to include the date.
The first koan strikes me as a timeless lesson about the advantages of the borrow checker. I came out of it feeling as if I had learned something, though it was really what I knew all along: it was one of the reasons I wrote rust.
The second felt tired. Of course OOP limits us to only objects!
The third I can see as a larger story about programming. As it applies to Rust? Perhaps it is saying that it is OK to crawl through the crack in the wall between the guards. Or it simply embodies the culture of Rust at the time - one of a massive group of people getting their hands dirty and getting shit done.
The fourth truly made me smile. Yes, its lesson about macros was correct - at least then. I doubt they have gotten much easier to use. However, they are useful to work with to get a feel for manipulating the raw syntax of the language. Writing some toys with them is sure to teach you a thing or two about using the language without them.
The second is about the separation of data and behaviour—Rust has structs, which are purely data, and implementations, inherent and of traits, which provide the behaviour. This is more flexible than classes which unite the two and tend to require that everything be in one place, and a Good Thing.
Meh. Seems pretty 1:1 to me. Skipping the complexity with runtime polymorphism[1], there's no meaningful semantic difference between a Java interface or mixin-style C++ superclass or [insert abstraction from your favorite language here].
Frankly this is one of the bits of Rust that infuriates me, because while it's not a big deal nor hard to understand, it's senselessly different from the way the rest of the world does things. It's another obstacle to new programmers, in a language that is filled with booby traps for the newbie.
[1] Let me state upfront the degree to which I am completely uninterested in debating the merits of multiple virtual inheritance vs. trait objects. They both suck. I guess if I had to pick C++ sucks a tiny bit less because you can implement something like a trait object in straightforward code, where Rust can't do vtables without boilerplating every method.
> it's senselessly different from the way the rest of the world does things.
Because every way of doing "x" has already been tried and the way we do things _right_ _now_ is hands down the absolute best way right?
Less snarkily, I think experimentation is good - we come up with new approaches and ways of doing things (which could solve any number of previously difficult problems) and helps prevent "monocultural" approaches to things.
> It's another obstacle to new programmers...
Why is it an obstacle rather than just something they learn? You could just as easily argue that the OO approach of wrapping up structure and functionality in a single object is equally "just another obstacle for new programmers".
> in a language that is filled with booby traps for the newbie.
That's a bit of an unfair statement, Rust has a learning curve, but it is certainly not filled with booby traps: it goes to great pains to make things transparent and be upfront about things. C++, JS or PHP are languages that I'd call filled with booby traps for beginners...
> Because every way of doing "x" has already been tried and the way we do things _right_ _now_ is hands down the absolute best way right?
Absent evidence to the contrary, yeah. Generations of hackers have been expressing designs perfectly well with traditional class syntaxes. This is a long solved problem, and a skill you can rely on when moving between C++ or Java or C# or python or Ruby or JS (though Javascript tried to get fancy in this space too and had to bolt on traditional syntax later). But to get stuff working in rust you have to learn a different metaphor. That's bad a priori unless there's a clear advantage. And be real: there isn't, it's just syntax churn.
> Absent evidence to the contrary, yeah. Generations of hackers have been expressing designs perfectly well with traditional class syntaxes.
On the contrary, what Rust does is a direct counter to the most notorious pitfalls of "extends" inheritance. Generations of OO experts and advocates have gone on at great length about "has a" versus "is a" relationships, about the importance of favouring composition over inheritance, about "SOLID". But these things are only communicated by oral tradition, so they remain as booby traps for every newcomer learning to design a system. It's past time that languages did more to help those newcomers (and to be fair Rust isn't the first here: Go, Kotlin, and even Java (with its separate keyword for interfaces) all made significant progress in this direction).
> Generations of hackers have been expressing designs perfectly well with traditional class syntaxes.
“Traditional class syntaxes” have existed for only half the time since people started thinking about object-oriented programming in the 1950s— C++ was only invented in 1983, and didn’t get popular until the mid-90s. That puts it in widespread use for only one generation, and about due to be supplanted by the next major paradigm (maybe async/promises/futures).
It won’t go away, of course: structured, functional, and procedural programming are all standard tools used by most programmers today alongside object orientation. We just have enough experience with them to know what problems each is best and worst suited for, and this is what you’re seeing in Rust; it treats OOP as one useful tool in the toolbox instead of a panacea that makes everything better.
Rust traits are similar to features in many different languages: Swift protocols, Haskell type classes, Scala traits (when used as implicit evidence), even Go interfaces (in an approximate order from most to least similar). It's not the OOP way of doing things, but it's not senseless nor is it uniquely different.
Another practical difference is that you can implement multiple traits for a given struct which require a method with the same name and same/different signature. The compiler always knows which trait implementation the code is calling so the names implementations don't clash.
Not to pick on you two: but a feature that implements the same concept modulo changes to the source code like friend declarations (first example) or naming (second) is the very definition of a merely syntactic difference. Rust represents the same stuff, it just does it in funny ways. That was my point.
Yes, I avoided the “semantic” question as it seems very slippery, it could easily devolve to “Rust is turing complete so it is just the same as any other language”.
Out of interest, do you have any examples that you would consider semantically different, while still being appropriate for day-to-day programming?
Like the borrow checker story, Rust's separation of composition, delegation, and interface implementation is not to protect them from you doing them, it's to protect you from what will happen if you do them when you didn't mean to. You talk approvingly of Java interfaces but those were controversial at the time for exactly the same reason ("why do you need a separate keyword? Just write a class full of pure-virtual functions, it's the same thing").
Hey it's not senseless. Rust doesn't have subtyping (except for lifetimes), and specially not subtyping between a parent class and its child. This is on purpose. Subtyping is required for the usual class based OOP but greatly complicates type inference.
However, we don't have yet means to emulate downcasting for trait objects, except by using the Any trait (which is a footgun). Until then, class based OOP is more expressive than whatever Rust is doing now.
I think you're right. I took the koan to mean that when designing any given widget the focus of the implementation should make as few contextual assumptions about the use of the widget as is it feasible to do.
There is an inverse relationship between the number of contextual assumptions made about a widget and the number of contexts the widget can operate in. Something with few contextual assumptions is typically referred to as "flexible."
Duck typing as a practice entails a focus on the capabilities of a given widget rather than the "role" (read: type) of that widget. Focus on capabilities = fewer contextual assumptions. Focus on role/type = more contextual assumptions. Thus duck typing can be thought of as one possible embodiment of the koan's intended perspective.
Of course, some detractors of static typing might take this to mean that static typing implies a focus on types which as I've just shown would imply more contextual assumptions and less flexibility. But as rust's traits show, one can keep the benefits of static typing without sacrificing flexibility so long as the focus of the overall design is still capability-centric rather than role/type-centric. But that's a different conversation :)
As someone who's been deep into rust for only a month, I can't imagine there was a time where macros were any _harder_ to figure out.
That said, using macros that other people have written for me is pretty great. The procedural macros from serde -- Serialize and Deserialize -- are phenomenal. I don't think Rust would have half the value to me writing web apps if I didn't have those.
> “A perfect interface is one which is impossible to use incorrectly, even by accident.”
It illustrates the point by showing how a non-expert can build a complex model from well-designed parts without even knowing what the end result should look like.
But then later:
> "... Even an ugly, rickety shed would be more useful than a hypothetical, flawless pagoda."
Documentation represents the compromise between a perfect interface and shipping pressure. The gap can be minimized but never eliminated.
I’ve worked on projects designed by architects. They had big, beautiful interfaces defined by types. When I implemented the interfaces, working systems magically appeared. But it was my job to implement the interfaces. They didn’t get organic adoption from other engineers. Nobody was voluntarily choosing to implement their system via the big beautiful interface. These projects ultimately failed.
Projects I’ve worked on that succeeded had small, comprehensible interfaces. Engineers can quickly understand how to use them and see what the benefits are. The pitch is “our service does this one thing you need,” not “build your entire project by implementing the interface of our type-safe general data processing system.”
It most definitely sounds like a problem with these other engineers. Maybe I'm a bit out of touch here, but my view is that if engineers are not implementing services as per the architect's specifications, then they are not doing their job. If you hire an architect to design your house, and end up with a run-down shack because the design was complex and the builders didn't feel like working to the plan you'd be within your rights to seek legal recourse against them.
Perhaps it’s all good when the implementers work for the architects, but when the architects are designing an interface that is going to be sold, it has to be something people want to use.
By the end of the first paragraph, I felt as though I was reading through Anathem again. I have never read any other writing that gave me flashbacks like that to Neal Stephenson's style.
Shoehorning ideas from eastern philosophy into coding... this needs to die. Just talk about best practices or something, stop pretending you're a monk when you're not.
I thought this was going to be similar to https://play.kotlinlang.org/koans/overview . I used that as a quick way when doing other things to slowly learn parts of the language before diving in, was hoping this would be similar.
Repetition is better than using a bad abstraction. While it has its issues, repetitive code is still idiomatic and readable code that can be understood with a working knowledge of the language. Introducing abstractions requires that the reader now understand the complexities of the abstraction. Introducing a sufficient number of bad or leaky abstractions can render code impossible to understand.
Good abstractions are difficult to create, especially when you must abstract over functionalities that are subtly different but not in a consistent manner.
Macros (like any code generation tool) are powerful but difficult to use. They are also arcane, in that they allow you to roam well beyond idiomatic style and to employ syntax of your own invention.
It follows then that in most scenarios where you imagine you could "clean up" your code with macros you in fact can't, or you might spend an unreasonable amount of effort trying with limited success. Even if you do succeed, you might render your code incomprehensible to anyone other than yourself in the course of your macro-fication.
She strive to have a code with no repetitions, just like their language. As such, if you try and go too far, it might become as understandable as their language.
They are also a great way to get yourself acquainted with a language - these specific Rust ones sound designed to teach specific lessons but when I was learning Scala I worked through their set of Koans and found it a great way to get used to types, syntax and generally the Scala way
The first koan strikes me as a timeless lesson about the advantages of the borrow checker. I came out of it feeling as if I had learned something, though it was really what I knew all along: it was one of the reasons I wrote rust.
The second felt tired. Of course OOP limits us to only objects!
The third I can see as a larger story about programming. As it applies to Rust? Perhaps it is saying that it is OK to crawl through the crack in the wall between the guards. Or it simply embodies the culture of Rust at the time - one of a massive group of people getting their hands dirty and getting shit done.
The fourth truly made me smile. Yes, its lesson about macros was correct - at least then. I doubt they have gotten much easier to use. However, they are useful to work with to get a feel for manipulating the raw syntax of the language. Writing some toys with them is sure to teach you a thing or two about using the language without them.