Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The following is purely my opinion.

Rust might have gone too far the other way. Yes it strives to be a modern language, with a lot of functional programming features and "OOP" done right (aka no inheritance, simply method polymorphism). To me Rust is more a C++ replacement than a C replacement.

A replacement for C should try to be as simple as possible while fixing C weak typing mess with strong typing for instance, including when it comes to pointers (for instance like in Ada where you can't just take the address of anything you want, you need to declare it as aliased to begin with). In fact Ada tries hard to make pointers redundant which is a great thing. This and range types and bound check arrays( Ada has a lot lot of great ideas, and "C style" Ada is a treat to use).

So a C replacement should keep the procedural nature of C, with closures perhaps, but not go further in terms of paradigm. C aficionados often claim they love C because it's "simple"(it isn't) That's what they mean, they love C because you can't really do OO with it.

On the other hand, Rust macros are exemplary of what a good macro system should be.



> To me Rust is more a C++ replacement than a C replacement.

People make this comparison a lot, but it’s not fair. Rust is far simpler, in terms of number of features of the language, to that of C++. It’s fair to say that Rust is far more expressive than C, but this just represents an initial learning curve that is higher than C, but not as high as C++. The type system is fairly simple in Rust, but it can be used to create very complex things. It does take time to be able to read it, but the guarantees you get from it are very powerful.

> a C replacement should keep the procedural nature of C, with closures perhaps, but not go further in terms of paradigm

Why leave all of the language development of the last ~50 years on the table? Iterators in Rust are amazing, algebraic data types (enums) allow for simple state machine constructs, lifetimes guarantee memory safety, default immutable memory and single mutable owner mean no more concurrent memory modifications. It’s safer than Java, and as fast and memory constrained as C, what’s not to love?

These are amazing features, that allow for amazingly stable programs. To put it succinctly, you get to code once, and then rely on that code for a long time, to focus on building features, not bugs.


The only thing I ask of any C-replacement languages is that they make it possible to describe ABIs, message layouts, and on-disk formats in detail. C historically does that well enough, though not really all that well (e.g., bitfields and enums have issues, and struct packing is not always easy to get right). There is a lot of value to this!

The ability to mix object code from multiple languages (FFI) is critical, and that's why C is the common denominator for FFIs.


I won't do this justice, so I'll point you to the Rust book on this subject: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe...

In short, at the moment, if you want a stable ABI, you must export an un_mangled C interface. This is easy enough. One of my favorite sites is this for FFI stuff: http://jakegoulding.com/rust-ffi-omnibus/

It covers a bunch of languages, and suggests techniques for overcoming any complexity in exposing higher order types.


I wasn't criticizing Rust, and I'm aware of what you linked. The point I was making is that this sort of functionality is a sine qua non for C-replacement languages. That Rust has it is to its credit, and one reason that I think it is a very good C-replacement language.


C is the common denominator for FFI for OS that happen to be written in straight C, which then mix the C ABI with OS ABI.

That is not true in mainframes, Windows (ongoing replacement of Win32 with COM, it is lot of fun to do COM in C, more so UWP), Android (JNI, AIDL), Web (JavaScript/WebAssembly), iOS/macOS (Objective-C runtime), Fuchsia (IDL).


Re: COM, that works because MSFT can a) commit to an ABI for C++, b) they can stick to a subset of C++ that makes that easy enough. Similarly for OS X.

The point isn't to stay in a C-like world. The point is that it must be possible to specify an ABI, file formats, and so on, in the host language. Yes, we can use external IDLs (even user them as DSLs if you like). I know all about and have worked with quite a lot of them (e.g., ASN.1, XDR, and many many others). But when you get down to a certain level, a light-weight approach really pays off. For example, at the system call level, and for many sufficiently simple file formats and such. At a higher end in complexity, on the other hand, then ASN.1 and friends pay off instead, mostly in a) obtaining interoperability with implementations written in other languages, b) managing extensibility.

Another thing is that some of us think about how things work at many levels. It's nice to be able to think about an interface and accurately picture what goes on at a low layer. It's difficult to do that with HLLs. But HLLs can easily have features that allow fine-grained control of or exposure of low level details, and that's what I'm saying I want to have as an option.


COM and WinRT are still based on the C ABI though. So anything that can do that, can do them - hopefully with more layers on top for convenience sake, of course...


Not quite, COM yes, although it is masochistic to do so without any higher level help.

UWP on the other hand extends COM to support inheritance and value types across calls, and the lowest Microsoft was willing to go on their high performance components was the Windows Runtime C++ Template Library, now replaced by C++/WinRT bindings and updated MIDL compiler.


The ABI is still C. Value types are just structs, and inheritance is implemented via aggregation - under the hood, it's all just interfaces and conventions on how they all play together. So any language that can do C FFI, can do WinRT. We did most of it with Python and ctypes as a prototype.

And you could even use that from C back in the day if you #included the "Windows.*.h" headers that shipped with the SDK - they had a bunch of #ifdef __cplusplus etc in them. Not sure if that stuff is still there or not.


Why would you think that Rust is "far simpler" than C++? Exactly the same concepts (with extras on Rust's side because of the ML-like constructs) must be learned for both, except they're distributed differently on the learning curve.

And I wouldn't say that Rust's safer than Java either. Memory access errors are basically non-existant in Java and it has quite robust concurrency primitives and libraries.


That’s based on my experience of working with C++ on and off over the last 18 years and reattempting to learn some of the modern pieces of it, as compared to learning and working with Rust over the last 3.

I find Rust simpler, more concise, and nearly no footguns.

Edit, I didn’t see your Java comment:

> I wouldn't say that Rust's safer than Java either.

Here’s why it is safer: It has the same set of guarantees about memory safety as Java. In terms of concurrency, it is safer because of the single mutable reference guarantees across threads, enabled by a better type system. The type system is stronger, which allows for more details about code to be put into types, like algebraic data types of Rust, without the limitations of Java’s non-generic enums.

And the big one: no bare nulls allowed. This reduces a huge set of logic errors. Now Java is getting Value types in a future release which will allow for a true Option type in Java, ie one that cannot itself be null, the other points still stand.

On top of that, in large applications the GC can become an Achilles Heel, where it eventually pauses the application with dire consequences. Rust of course has no GC. And a great side effect of this is that Drop (java Finalizers) actually work in Rust, which is amazing.

Basically for these reasons, Rust is safer than Java.


Well, that's still personal opinion, even if grounded in experience.

My personal opinion is that they have similar complexity, but in general Rust decides to clearly define behaviour as much as possible and fail fast, preferably at compile time. This can be seen when comparing features: memory management is there (supported by the borrow checker), smart pointers are there, references & pointers are there, generics are there (even if not exactly the same), integer and floating point handling are there, concurrency is there (compile-time checking is nice), macros are there (albeit different and perhaps less error prone), string types are more complex than cpp, enums are more complex (but also more powerful), operator overloading is there, iterators are clearly more complex (and the functional aspects in general), etc.

So the average programmer will have to learn a similar amount of concepts or even more in Rust's case.

Where Rust is indeed simpler is error handling and building.

--- I figured you would bring up concurrency, and while Rust's compile-time verification is really nice, Java also has a simpler form in threading annotations. Additionally, I believe it's rare to use such low-level primitives in Java.

Nulls (and type system holes in general) are a correctness issue, not a safety issue, unless we want to argue that all bugs are safety issues.

GC pauses are a performance issue, as you already know.

Saying that Rust threading is safer than Java's is technically correct and Rust is probably a bit safer than Java because of that, but it's hard to argue that Rust should be chosen for a project because of that (this is the whole point of saying X is safer than Y). Java is super-simple to learn compared to Rust.


> that's still personal opinion, even if grounded in experience

I just wanted to make it clear how and why I've formed that opinion.

> unless we want to argue that all bugs are safety issues

Yes, I can definitely be accused of expanding the definition of safety to include all things that cause programs to crash in horrible ways. I look at it as differing levels of guarantees the language makes for you, and Rust makes many more at compile time than others. This adds to the learning curve you mention, is it worth it? Completely depends on if you see those guarantees as valuable.


> Why would you think that Rust is "far simpler" than C++?

C++ as a language is a lot more complicated. It has a lot of features with weird corner cases that interact in unfortunate ways.

> And I wouldn't say that Rust's safer than Java either.

Depends on what you mean with safe. If you mean memory safe, then they are about as safe. Rust has fewer runtime failures due to concurrency and null pointers though.


Not sure why the above comment is downvoted. The number of features in C++ isn't the root of the problem with C++. The real problem with C++ is that it both has a lot of features and that all of those features interact in subtle and surprising ways. Even if Rust had as many features as C++ (which it doesn't, not by a long shot; Rust is a medium-sized language like Python (though with a far less forgiving learning curve than Python)), one of the design philosophies of Rust is that features should interact in predictable ways, and if two features can't interact in predictable ways, then the compiler should do its damndest to prevent them from being used together and explain why.


Rust also has features which interact in subtle and surprising ways. Generics and operator traits bumping into the "coherence" rules is my personal pet peave. I doubt any user would have predicted that interaction, and even though the compiler gives a detailed pointer to why, it's cold comfort.

Honestly, after hating C++ for years, trying to use Rust made me appreciate more about C++. I wish someone would make a language which took the best parts of both.


> even though the compiler gives a detailed pointer to why, it's cold comfort

We'll just have to agree to disagree, because having the compiler watch my back and preemptively guard against unforeseen pitfalls is about the warmest comfort I could ask for. :P


Generics and operator traits ought to be orthogonal and composable. Would you still be comforted if the compiler didn't let you mix for loops and arithmetic in one function? I mean, even if it gave you a detailed error message...


The biggest issue is not how complicated it is, rather there is no good way to prevent people to write C in C++.


Ok then please prove that C++ is more complicated than Rust by listing which features of C++ are not available in Rust. It's nice that the compiler can tell me when I do something wrong, but I still have to know how to write it correctly in the first place. In Java it's more or less code and forget.

And yes, I meant memory safety.


Besides the (desirable) features already listed by someone else, here are a few others, some of which can have unfortunate interactions: implicit conversions, constructors, member initializer lists, inheritance, overloading, default arguments, capture lists, variadic templates.


The biggest ones people ask for are non-type templates, HKT, and sometimes move constructors.


You can still get NullPointerExceptions in Java, frequently.

Rust doesn't let you do that, at least not without conciously deciding to use unwrap() everywhere.


> To me Rust is more a C++ replacement than a C replacement

Precisely. Rust is what C++ would be if it didn't need to be backwards compatible (though I'm not a fan of some of the syntax choices in Rust). Unfortunately, there's a reason C++ needs to retain backwards compatibility and its for that reason that Rust isn't going to replace C++ anytime soon.

> That's what they mean, they love C because you can't really do OO with it.

Until they invent GObject or one of the other OO on C libraries. The anti-C++ sentiment is more because compiler support was really quite dire until fairly recently (C++ 11).


The good thing about no-runtime languages is that you can mix modules written in them, within reason.

So Rust can make inroads into existing C++ code bases where it makes sense faster than we think, without replacing C++ wholesale for a long time. Firefox itsef is likely such a codebase.


That's harder than you would think due to name mangling.

Basically they can only talk to each other through a C interface.


I've been programming C on and off for 30 years. Rust feels too complex to be a C replacement. C++, yes...

C's paradigm is really about working with memory. Anything that restricts memory access with bound or type checking is going to feel like "not C"...


C doesn't have anything to say about whether bounds are checked or not. That's why so many things, including pointers to arbitrary locations in memory, are undefined: the standard was built to accommodate both embedded system implementations where scribbling all over RAM is desirable, and implementations that do things like bound checking.


While I am not a fan of it, D's BetterC mode kind of fits what you are looking for.

https://dlang.org/spec/betterc.html

It's kind of a subset of D language, without dependency on GC or runtime. It feels like cleaned up C. Of course without runtime, most of the standard library doesn't work, so you are on your own for the most part, but then, C doesn't really have that much of a standard library to begin with.


> C aficionados often claim they love C because it's "simple"(it isn't) That's what they mean, they love C because you can't really do OO with it

I do not think C programmers are anti OO. Infact, a lot of C patterns are modeled on OO (struct + function). I think the appeal of C is that you are able to write the fastest implementation any given algorithm, something that just isn't possible in most other languages.


> I think the appeal of C is that you are able to write the fastest implementation any given algorithm, something that just isn't possible in most other languages.

Maybe you can, but lots of the C code I've seen in the last years was pretty inefficient compared to what one would have gotten from a reasonable C++ or Rust implementation:

Examples are inefficient strlen() operations due to the default "string" type, unnecessary copies and allocations of things like strings due to ambiguous ownership, unnecessary null checks to quiet down static analyzers in the absence of non-null references, and extra indirections or allocations in order to work-around missing compile-time generics (besides macros), etc.


> I think the appeal of C is that you are able to write the fastest implementation any given algorithm, something that just isn't possible in most other languages.

If you told this out loud during the 80's and early 90's everyone would just laugh.


True, and I meant this in the context where you could embed assembly into your functions if needed.


That's not a very satisfying answer, as it doesn't apply to the languages typically compared with C- C++, Rust, or even D, Nim, Zig, etc.


I don't think struct + function is any more OO than tuple + lambda is. That's a very shallow definition of OO.


Well, that's how C++ does everything…


You don't need to use any polymorphism (generics or traits) in Rust. You can use it as a pure structs and functions language like C (especially if you're not using the standard library).

The argument in favour of at least some polymorphism is that it helps to minimise highly repetitive code that would be avoided in C through aliasing, type punning or other pointer conversion.


I love Rust, but I don't think that using a subset of Rust is a real option at this point. You'd have to stop using other libraries and even the standard library for this - and while it's possible, it definetly won't make your life easier. Rust has a wonderful community, but it's mostly filled with enthusiasts who're actively using nightly, so if you try to do things in a more limited way, you'll be on your own very often - much more often than for example I, personally, would be comfortable.


While many people use nightly, the statistics show that most use stable, with some using nightly in addition. You just hear about nightly more.


This is true, but doesn't refute my point. If you want not only to write something in Rust, but also to participate in the community and get help from it, it's much easier to do it if you're using nightly.

I consider myself an experienced developer, but unlike other new technologies, which I almost always learn just from documentation, with Rust I often turn to community for help. I understand that this complexity is neccessary and I wouldn't Rust to be easier, of course. But still, with Rust's complexity and overwhelming amount of information that's already out of date with the best practices - it sometimes seems that over a half of google results on stack overflow are still about pre-1.0 Rust. And when we (people who learn Rust) turn to the community, it usually tells us (in the most helpful and nice way, of course) "just use nightly".


Yes, it is a bit unfortunate that many suggest nightly. It makes sense, given that they’re enthusiasts, and therefore know all about the latest and greatest. But, these days, it’s becoming rarer and rarer to actually need it. But that’s only generally; it’s true that embedded often still needs nightly, though that should change pretty soon!

I wish Google didn’t customize results so much; I rarely see anything pre-1.0. It really makes it hard to figure out how to best help :/


Is that still true for libraries? I think that's what he meant, not all rust users as a whole. My impression certainly agrees with him.


If a library uses nightly, then you have to use nightly to use it.

At this point, the only major library that requires nightly is Rocket, and Actix-web has been rapidly ascendant due to (rumordly) being used in production by Microsoft, and working on stable.

The data, from last year’s survey, (which means even more stuff was nightly only, for example, the RLS) where you can choose multiple options https://blog.rust-lang.org/images/2017-09-05-Rust-2017-Surve...


> You don't need to use any polymorphism (generics or traits) in Rust. You can use it as a pure structs and functions language like C

This just means that the Rust language does not have the feature of being simple. Of course, there are sane subsets of C++ also (e.g., do not use new/delete), but this does not mean that C++ is a sane language. The same goes for Rust, probably.


> The same goes for Rust, probably.

Well, at least you have the good grace to admit that you haven't used Rust. :P


I've used Rust. It's not simple. For instance, lifetime annotations are ugly and confusing.

What a snarky reply from someone who already knows that Rust is not simple. The person you replied to was right, and yet you cast doubt on what he guessed to be true.


The person that response was directed at was attempting to use a mistaken similarity to C++ to paint Rust as a language that is not "sane". You appear to be injecting your own interpretation as to what this subthread is about. :)


I assumed you were contradicting this quote:

> This just means that the Rust language does not have the feature of being simple.

I really don't know what it means for a language to be sane, so it didn't make any sense to me that you were replying to that part. My bad.


This is exactly what c++ tried to do and failed.

Of cause you can find a sane subset of c++ / rust that most comfortable with. But once your project accept c++ / rust, nothing can stop them using the other "features" of the language.

Trying to enforce code style drain lots of energy from the project. Maybe a good linter can help......


While I'm not a fan of polymorphism and actually avoid OO completely (except for making data structures out of classes of course) I feel like this could be something easily solved with tools, if making tools were easy.


That was the argument of C++ advocates in the 90s.


Considering that C doesn't have any good facilities for polymorphism, it's going to continue to be the argument of any language that succeeds C. Unless, that is, that successor language also doesn't have any good facilities for polymorphism, in which case we have Go as a fascinating real-world study in how people will incessantly demand them and deride the language for lacking them (which is saying something, considering that interface{} in Go is already better than pretty much anything one would hack up in C via void pointers or macros).


Apparently under certain conditions it is possible to emulate a crude form of ad hoc polymorphism in C.

https://codegolf.stackexchange.com/questions/2203/tips-for-g...


Wasn't it originally a much bigger deal to be able to have the facilities for more generic programming before templates? These days people's threshold for bloat seems much higher, while the penalty for allocation and jumping around in memory are much higher as well.


I presume that you are defining C++'s virtual functions as "not a good facility for polymorphism". Why?


OP said nothing about C++, just plain C.


Ah, I see. I agree.


At least C11 has light generics support.


It was just recently that C++ got some underpowered kind of generics (beyond templates). So, no, I don't think anybody advocated that in the 90's.

Late 90's advocacy was much more on the lines of "OOP lets you reuse code" and "widget libraries are great, and they all but require OOP". One of those is blatantly correct.


> A replacement for C should try to be as simple as possible while fixing C [...]

I have a feeling that such a language will offer too little to offset the costs of switching


I have always kind of wondered why Ada did not catch on as a systems programming language (I think it was designed to fill that niche as well).


1. At the time (i.e. pre-C++), it was a gigantic language. Further, because of the Ada policies (IIRC), you could not subset the language; you had to include the full multi-process model to call it "Ada", for example.

2. There were no good, cheaply available implementations of Ada. GNAT was originally quite awkward and finicky when I first poked at it, years after seeing Ada in a class (and I can't remember what compiler we used there).

3. Most importantly, no common, popular systems picked it up as the preferred systems programming language. MacOS had Pascal for a while, Windows went with C or C++.


Rational Software actually started their business selling Ada Machines, yep just like Lisp Machines but with Ada, but they pivoted into selling just compilers.


> OOP" done right (aka no inheritance, simply method polymorphism).

Why do you consider inheritance a bad feature of OOP?


On a very high level, in the vast majority of cases, you bring along lot of baggage, both in code and mental, that you don't need, and compared to some alternatives, such as composition, it is simply far less flexible. Inheritance fits a much smaller subset of problems than people think (one of them is GUI design, a problem inheritance fits with nicely). But like everything in this industry, it tries to get shoe-horned in to solve every problem.


It's surprising when I go into interviews and the people interviewing me do not have enough experience working with object oriented code (in the languages I know) to have come to realize this - it's one of those things it seems to have to be relearned over and over.


Think about it as the difference between only inheriting functionality (interfaces in Java 1.8+, Rust traits, etc) vs inheriting data.

OOP can become spaghetti very quickly. Especially as people mistakenly allow children to modify parent memory directly, protected non-final fields in Java. Once that happens and as the inheritance higherarchy gets deep, understanding what a field actually is at any point becomes very difficult.

Interface and trait inheritance at least removes one piece of confusion by only allowing functions to be inherited, which makes code much easier to reason about.


Inheritance says that a derived class is a subtype of its base class (this is formalized in the Liskov Substitution Principle). To me, this is strongly coupling two things that don't have a very good reason to be coupled: Code organization and expression substitutability.

Sometimes I want to derive a supertype. Sometimes I want to re-use behavior but I don't want the new type to be expression-compatible at all. But inheritance says that if I'm re-using code, I must accept certain expression substitution relationships between my code and the code I'm re-using.

As with anything, there are reasons to use it and situations where it makes sense. But it just seems like a very niche connection to use as the basis of a language design.


In my personal experience, overriding non-abstract methods is definetly the worst feature of OOP. Too often comlpex codebases have multi-level overrides, some call base method in the beginning, some call it in the end, and some none at all - and almost always this leads to incoherent contracts and behaviour.

Interface-only inheritance and composition instead of inheritance (with components or otherwise) is almost always simpler and thus more error-proof. Even if sometimes you have to repeat yourself a couple times, decrease in mental complexity is definetly worth it.


Say you decide to make a video game, and you decide to make a couple of classes for your game objects to inherit from. You make the classes Tool and Weapon. You have just prevented yourself from making the game Clue. The solutions to this tend to be ugly, and your object relations will tend to change over time, often in ways that are difficult to reconcile. Thus, GoF suggests to prefer composition instead of inheritance.


GoF?


The "Gang of Four", the four authors who wrote the classic "Design Patterns" book, (subtitled "elements of re-usable object-oriented software").


As Wildgoose says, it's a frequently-used initialism for the Design Patterns book, which is otherwise somewhat awkward to refer to concisely and unambiguously.


> "OOP" done right

I wouldn't call the Rust semantics "OOP". But it has a pretty complete implementation of type classes, with class inheritance.


Zig again.


I think it will only happen the way of Luddites in systems programming.

Even if redo Modula-2 with curly brackets I am not sure it would pick up.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: