9 ms·
I grew up when OOP was the shit. Everyone wanted to add OOP to every language. C++, ObjC then Java. I absolutely hated, not because OOP is bad, but because the
by noncoml 7y ago
I grew up when OOP was the shit. Everyone wanted to add OOP to every language. C++, ObjC then Java.
I absolutely hated, not because OOP is bad, but because they never really manage to blend it just right with static typing. But I was the odd one. Everybody else seemed to love it and use it for everything.
I was lucky enough to outlive this OOP craziness.
Now I am seeing the same craziness with “a sync” and continuations and wonder if I will be lucky enough to outlive it to.
fn get_user(&self) -> Pin<Box<dyn Future<Output = User> + Send + '_>>;
This is plain craziness.
- DougBTX 7y ago> This is plain craziness. This example from the article was to demonstrate a type that was more complex than would be acceptable in day-to-day use, so they agree with you here, it would be crazy to make that required to use the feature.
- rosybox 7y agoJava has always been an object oriented programming language. A class is a basic construct of the Java language. It wasn't added into Java. People still use classes. All the major new programming language have OOP features, you can create instances in Swift, Kotlin, Go and Rust for example. async language features are just inline co-routines. Inline co-routines are a useful concept. It's a single tool in a big toolbox of other tools and not every problem requires the same tool, just like OOP features. It's just more stuff you can use if you want. If you don't want it, you don't have to use it, you can just write Haskell or OCaml without using objects and not worry about it and you won't have to worry about how long you live.
- noncoml 7y ago> Java has always been an object oriented programming language And C++ has always been an “OOP” language. But they are both a dumped down version of OOP as they both don’t have metaclass support or extreme late binding. For something more aligned to OOP look at smalltalk or Ruby
- viraptor 7y agoIf you grab specific favourite functionality, this quickly becomes a no-true-scotsman situation. We know what OOP looks like in general. First-class metaclasses support is not available in Java, but if you really need it for some specific cases, you do them at compile time using annotations processor. The extreme-late-binding as Alan Key would like it does indeed make Java an inferior OOP. Service registration does this on a macro scale though. (although I've seen more abuse of micro-scale extreme late binding than use of it in good design)
- continuational 7y agoIn all fairness, in other languages this usually looks like def getUser() : Future[User]
- noncoml 7y agoNot in dynamic languages, ie JavaScript
- littlestymaar 7y agoIt is. It's not visible if you the types aren't written, but under the hood, that's the type of things like this.
- noncoml 7y agoEhm. No. JavaScript will return a Promise. It’s “type” is Object as pretty much anything else in JavaScript.
- endless1234 7y agoBut isn't a Promise for all intents and purposes the same as a Future here?
- marcoms 7y agoYes, but I think they are emphasising that only a promise will be returned. Any further type information is not given
- wwright 7y agoCheck out structural typing. I would say it’s the type system JavaScript uses for non-primitive values. The “dynamic” appearance is just that JavaScript doesn’t center the type system in its UX.
- cerebellum42 7y agoEven in Rust, it only looks like that in this very specific use case and is usually completely hidden from the programmer. It's the desugared output of a library. Usually it would look like this in Rust: fn get_user(&self) -> impl Future<Output = User>; This isn't exactly the same functionally, but for most use cases it works just fine. The difference is that this uses static dispatch instead of dynamic dispatch and therefore can't support dynamically returning two different structs that both implement Future, it has to be one type.
- jdub 7y agoBut what you've quoted is what's "kept under the hood" with today's library-level support for async traits.
- dtech 7y agoThis is a silly argument, of course de-sugaring language leads to a less nice type signature. C functions compiles down to assembly and C++ or Java hide vtables and such when you do method calls. If you go lower in the abstraction level you get more complicated mechanics, and that says nothing over the validity of the high level semantics.
- fauigerzigerk 7y ago>C functions compiles down to assembly and C++ or Java hide vtables and such when you do method calls. I don't think it makes sense to compare the output of Rust macros to assembly code generated by a C compiler or to vtables. Macros are part of the Rust language, as is their output. Understanding Rust means understanding both input and output. So this abstraction boundary is intentionally leaky to some degree. Generated assembly and vtables on the other hand are compiler implementation details subject to change without notice. Any abstraction leakage is unintended and undesirable (even if developers sometimes benefit from understanding that output) Your line of reasoning makes all debates about language complexity completely pointless.
- likeliv 7y agoI disagree that the user of the macro needs to understand its output. The output of a macro is an implementation detail, and the documentation of the macro should be enough to use the macro without even looking at its output. For example, no need to understand the magic behind the 'quote!' or the #[async_trait] macros to use them.
- fauigerzigerk 7y agoNot every user has to understand every macro. But the output of a Rust macro is valid Rust code whereas the output of a C compiler is not valid C code. As a consequence, criticising the complexity of whatever a C compiler generates cannot possibly be valid criticism of C's complexity on a _semantic_ level whereas criticising the output of a Rust macro can be valid criticism of Rust on a semantic level.
- GlitchMr 7y agoIt's not really all that crazy. Sure, if you consider languages like C#, it's much more verbose than those, but that's because Rust tries to deal with async without garbage collection. This isn't a typical code you would write in Rust, in fact, in this case, this type was generated by a library. Dealing with this "ridiculous" type step by step: Future<Output = User> This is a future. It returns an user. dyn However, because the exact `Future` implementation can vary, dynamic dispatch is necessary. Use of `dyn` keyword explicitly says that a vtable is necessary. + Send Rust is supposed to make programs free from data races. It's very much possible to have objects which aren't allowed to change the thread they are in. For instance, `Rc<T>` (single-thread reference-counting pointer) cannot be sent to other threads due to it using non-atomic reference count. In this case, using `+ Send` says that whatever future this method returns must be possible to send to other threads. This prevents use of objects like `Rc` within the futures, and allows the future to be used in multi-threaded executors. + '_ The method borrows `self` (as seen by the use of `&self`). Saying `+ '_` means that the future should borrow `self`. This is necessary due to lifetimes. Box<...> Because dynamic dispatch types (note `dyn` before) can have varying sizes (one future can be smaller, another one can be bigger), it's necessary to wrap them somehow so that they could be represented in memory. In this case, the simplest heap-allocation type (`unique_ptr` in C++) is being used. There are other options, like `Arc<...>`, but in this case, `Box` is sufficient. Pin<...> Futures can store pointers to its local fields. This is going to cause issues if the allocation could be moved somewhere else in the RAM - the future would be moved to the new location, but the pointers would still refer to RAM in the old position. Using `Pin` prevents the user from moving the contents of `Box` to another place in the RAM.
- pron 7y agoThis is a nice demonstration of the tradeoff inherent in the (confusingly named) "zero-cost abstractions" philosophy: it brings accidental complexity front and center. This accidental complexity is often viral, propagating through the call stack in the type signature, and made so pervasive in the program that it becomes its focus (even if the types are hidden and inferred) and the element that is hardest to change rather than easiest. C++ is most famous for this philosophy, but Rust doubles down on it because of its commitment to memory-safety soundness through the type system. Look at how many details having to do not with any algorithm but with the code the compiler emits are stated here: that the call is made in an asynchronous context, that an object requires a vtable, that an object is able to change threads, the object's lifetime, boxing, and that the object cannot be moved in RAM. The only part that's related to the algorithm here is `User`. Of course, in constrained environments like embedded systems or other circumstances where control over resources is crucial -- the domains Rust is designed to target -- accidental complexity becomes essential as control over resources is an important component of the problem. Some more here, and an alternative (with it's own tradeoffs) that could be called "zero-cost use" (although that's equally as bad and confusing as "zero-cost abstractions"): https://news.ycombinator.com/item?id=19932753 https://news.ycombinator.com/item?id=19932753
- ht85 7y agoCrazy? What's crazy to me is that a compiler can statically guarantee type soundness AND memory safety around asynchronous code.
- zzzcpan 7y ago> Now I am seeing the same craziness with “a sync” and continuations and wonder if I will be lucky enough to outlive it to. Maybe you will. It's an attempt to address shortcomings of shared memory, just like borrow checker. Once languages start moving away from shared memory to actors, there will be no need for any of it. But it could take a long time, the industry dug itself way too deep into the whole shared memory world.
- jsjohnst 7y ago> Everyone wanted to add OOP to every language. C++, ObjC then Java. By mentioning the languages after that comment, was it your intention to imply that OOP were added to them? Because to my knowledge, all three were OOP based from the very beginning. Please comment with a source if you feel I’m wrong on saying any of those were OOP from the beginning. I (as well as others I’m sure) would enjoy the learning opportunity.
- z3t4 7y agoC++ and ObjC came from wanting OOP in C. I dunno about Java
- Rusky 7y agoThat example is generated by a macro, not part of anyone's actual program source...