C++ is the next C++
open-std.org
open-std.org
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
Absolutely.
Back in 2010, I read the c++faq religiously, tried to warp my projects to it in college, and in the end produced nothing because i was too busy trying to be a big boy. Now, the c++faq is obsolete. I'm generally tired of opinionated lists of "best practices," code that actually works is 10x better than what some guy (always a guy) somewhere thinks I should do whereas in fact what I should do is actually build things that I can use.
Caveat (big one) okay, it's not "some guy", the authors are literally Bjarne Stroustrup & Herb Sutter. I still feel anxiety though.
The authors of those guidelines are Bjarne Stroustrup & Herb Sutter, who kinda have more than average C++ experience. The guidelines are generally very well reasoned with motivating examples & practical exceptions to the rules. They are generally not of the opinionated "style guide" or "ban these things" camp that a typical set of "guidelines" tends to fall into.
I think the only CppCoreGuideline rule I disagree with is it wants to use T& for out params whereas I prefer T* so that at the call site there's a more obvious indicator of potential mutability.
Month month() const; // do
int month(); // don't
I know I am in the outlier here but I very much prefer the second way of writing code. Sure the first one gives more compile-time safety but as someone who has to read the code, I have to go through an additional level of indirection before I truly understand how Month is implemented.Others might offer a counterpoint that hiding such implementation details is exactly the point of OOP. True but as a developer who has to read and fix code, I do have to worry about the implementation details because the bugs could be in implementation details and hiding simple integers behind an extra layer of types adds to one more step when I am trying to reach the implementation details and fix bugs there. Multiply this experience by the number of all the layers in the code and maybe others might be able to appreciate why I prefer the second type of coding. Please someone tell me I am not the only one who feels this way!
With ints as months is 0 or 1 January? What about -1 and 13, do they have sensible meaning? You need to re-apply that effort constantly to any int that might be a month. This isn't a hidden implementation this is an easy mistake waiting to happen. Using a month enum solves this, and you are not fixing bugs in the implementation of an enum unless you are a compiler developer.
Same with const, it prevents accidental changes to the class from this member function. It allows more sensible code and compiler optimizations. The "do" line will almost always be faster than the "don't" line. There is no way adding const changes the amount of effort for a reader in a negative way.
Please stop sharing my secrets. I've done pretty well fixing up things with basic type safety because my predecessors couldn't be bothered to use their brains. I like being employed.
Unfortunately no, const only tells the compiler optimizer something when applied to variables but not for reference or pointer types (or member functions, where it just means apply const to the this pointer). It would be nice if there were a stronger co_const that would tell the compiler could assume is never cast away (or at least that the variable is not modified through that pointer/reference even if the const is cast away) but C++ const is not that.
> I have to go through an additional level of indirection before I truly understand how Month is implemented
I'd just hover over it in my IDE and it'd tell me what it is. Regardless, "Month" tells me it's a "month" whereas "int" tells me "lol get fucked I ain't telling you shit"
The "do" example there is much more readable than the "don't" example. Especially in any further usages of it, as it's one less thing I need to keep track of. That is, I don't need to second guess if the variable was passed to the wrong thing since the compiler is validating that for me. I don't need to track that during my "reading"
> True but as a developer who has to read and fix code, I do have to worry about the implementation details because the bugs could be in implementation details
Then why are you arguing against it? A "Month" type cleanly restricts the implementation details to a specific controllable set, which an "int" does not and is a free for all. You're in for a nightmare trying to fix people doing math with dates if you use "ints", whereas you can pretty easily fix all of the users of a "Month" or "Date" type by just removing the math operators (or "fixing" them to work appropriately) and letting the compiler spot all the mistakes.
I disagree. Is "Month" a 1-based month ordinal? A 0-based month index? A string containing user input that should be preserved in it's original form to avoid data loss (be that "jan", "JAN", "January", "1", ...)? Memoized? An indirect reference to OCR data? Do I even have a guarantee it's on the Gregorian calendar, or am I in the context of some calendar app that might support chinese lunar calendars? Is it JSON serializable? Is it a localization placeholder? Is it an integer typedef that means different things in different contexts, including "months ago" in some relative timestamp formatting code?
"Month" tells me jack shit that the function and variable names didn't. "int" at least tells me I can math it, and that if I'm dumb enough to have memcpy-serialized code, I have a potential porting hazard in the form of endian issues - and size variance if I'm dumb enough to target an ILP64 platform. Based on personal experience, these are far more likely bugs for me to encounter and need to fix than any that would be caught by stronger month typing. There is a place for stronger types - I'm not complaining about Rust's SystemTime/Instant/Duration trifecta and more - but "Month" ain't it. It's a bridge too far, too pointless - it is the wrong abstraction.
And, yes, you can math on months. For indexing into arrays of localized strings, for conversion to/from epoch... I've written some really nasty .natvis visualizers that rendered absolute timestamps as PST timestamps for debug convenience, and it involved a whole lot of rather redundant and duplicated math in that horror of an XML format which would be made 10 times worse by extra "help" in the form of strong types to be unwrapped.
> I'd just hover over it in my IDE and it'd tell me what it is.
This works until some jerk buries it in #ifdef soup and causes your IDE to lie. Perhaps uint8_t - except on windows where it might be int for "backwards compatability". Or perhaps multiple definitions in different independent contexts (be that different namespaces or different included headers.) I've seen stupider shit frequently enough that the habit of spending more time performing a more thorough check than a simple hover will be time well spent.
> you can pretty easily fix all of the users of a "Month" type
Nah, that kind of underbaked leaky misabstraction isn't going to be properly thought out well enough to simply fix by merely poking at the type (and I'm confident in assuming it will be an underbaked leaky misabstraction on account of the lack of value provided.) You're going to have to actually audit all use of that type for dumb abstraction-breaking assumptions that make it brittle and fragile if you so much as sneeze on it's layout. A saving grace here: so little code will actually bother to use it, that it actually shouldn't be too terrible. Probably.
You can look up types, with a bare int you have to pray someone documented it and that they were consistent.
In the case of "Month", yes.
Give me stronger types for dates or timespans or instants though.
> Does the int handle rollover? Nope, you have to.
Rollover tends to need handling at the date level to carry the year, so let's be honest, "Month" won't handle rollover properly on it's own either.
So the guideline you're vigorously arguing against you don't actually disagree with at all, you just really, really hate Month for seemingly no reason.
Maybe give the guideline a read rather than just the 2 line snippet someone else posted?
> "Month" won't handle rollover properly on it's own either.
It does by not offering math operators in the first place, avoiding the entire possibility of a rollover from the outset.
Or it could throw on rollover so it's at least an runtime exception instead of a silent failure.
Unlike with an bare int, a "Month" type has places to actually document all that. So your counter argument, which is entirely the same for an int anyway, is utterly absurd.
> This works until some jerk buries it in #ifdef soup and causes your IDE to lie.
I don't really know what your point even is here. Your ide or code base is bad, therefore everyone should share your pain?
If your ide can't resolve it then go look up the docs the old fashioned way. It's the same end result of a wrapper type being vastly superior to a bare int. I can make a comment in a header or a man page or whatever for a "Month" type to answer all your questions. I could never do the same for an "int" variable.
It really sounds like you're just wanting to obsessively nitpick the specific example of "Month" than anything about what the guideline is even saying. If you're just too lazy to make a Month then sure knock yourself out. But at least be honest that you're just being lazy in the now with a hope it doesn't bite you later, don't pretend you're making an actually better or more readable decision. We all take the lazy out from time to time.
Good chance it won't actually be documented there though, or that the documentation will lie. Similarly, that documentation can easily live on a date-style object, where it's more likely to be accurate.
> So your counter argument, which is entirely the same for an int anyway, is utterly absurd.
No, I've eliminated several possibilities with a plain int that you've conveniently left unquoted. I do still have some ambiguity, but less, and gained clarity in several things you've - again - conveniently left unquoted. It's not perfect, but it's an improvement.
> I don't really know what your point even is here. Your ide or code base is bad, therefore everyone should share your pain?
Every codebase has some bad code, and my point is - even with good IDE assistence - a typedef still requires digging. And if my own personal experience is anything to go by, most codebases have a lot of bad code.
If you've been blessed with only perfect codebases handed down to you from the gods themselves, pristine and pure, maybe Month is less opaque for you than it is to me. You have my sympathy for the shock and horror you'll experience should you ever touch code writtten by mere mortals ;)
> It's the same end result of a wrapper type being vastly superior to a bare int.
I challenge you to share a single real life bug that you've managed to catch or prevent with a strong month type. Go on, I'll wait. Or show me some code from a real codebase where a stronger month type actually improves readability more than, say, a variable named month_index or month_no. Show me a concrete example of this vast superiority improving the readability of a function in a real codebase. Or where it's lack has made things confusing, and the mere addition of "Month" would provide clarity.
> I could never do the same for an "int" variable.
You can absolutely attach comments and documentation to variables, methods, and properties.
https://learn.microsoft.com/en-us/dotnet/api/system.datetime...
Oh gee, is that an `int` month property that states it's range? Yes. Yes it is. And I don't have to drill further into the documentation to look at the type of the property, and can just look at the property? Bonus points!
> It really sounds like you're just wanting to obsessively nitpick the specific example of "Month" than anything about what the guideline is even saying.
I'm providing context to point out the limits of the guideline and where it goes too far, using it's own example. Hardly obsessive nitpicking.
> If you're just too lazy to make a Month then sure knock yourself out. But at least be honest that you're just being lazy in the now with a hope it doesn't bite you later, don't pretend you're making an actually better or more readable decision. We all take the lazy out from time to time.
Don't get me wrong, I'm incredibly lazy. Otherwise known as cost efficient. I have better things to do than to create or untangle a mess of opaque types that do little more than obfuscate the code for some hypothetical type safety that - based on personal experience - won't actually add any safety in practice, won't catch any bugs, won't provide a meaningful improvement to documentation or code readability, and will just slow me down and take time away from working on something that might actually be important (perhaps some practical type safety.)
I would hope my coworkers also have better things to do with their time as well. The possibilities and backlog are infinite, but our time is not.
The most natural fit for the Month type here is an enum, enum class or something that wraps one with additional functionality. And while sure you could have enum Month { }; and then use Month(0) for january or whatever, that's just deliberate obfuscation and not what anyone reasonable would do for normal code. And it's still not worse than an int.
> No, I've eliminated several possibilities with a plain int that you've conveniently left unquoted.
Do you? Someone could have added a #define int float after all. Your arguments that just because someone could obfuscate the type in some way that that inherently makes using a Month type bad is just that: absurd.
> Oh gee, is that an `int` month property that states it's range? Yes. Yes it is. And I don't have to drill further into the documentation to look at the type of the property, and can just look at the property? Bonus points!
Except if you are assingning one month property from another month property you have to check the documentation for both that they math (and not just almost match). Whereas with a strong type the compiler checks that for you.
And I remain unconvinced that any of those "natural fits" add value.
> Do you?
Yes.
> Someone could have added a #define int float after all.
That would be straight up undefined behavior. Can't redefine keywords. Even a badly defined "i32" is clearly buggy enough that even the most foolish coworkers won't do it, and cleaning up after the outright malicious is at least more straightforward.
You're trying to say that the likelyhood of stupid tech debt from weird backwards compatability nonsense, and straight up intentional chaos, but I don't buy that you even believe it yourself. I have seen plenty of the former in real codebases, and a tiny fraction of the latter. Even the broken-ass codebases that I've worked on that have invoked undefined behavior by redefining keywords haven't gone as dumb as #define int float.
I've seen #define true 1 though.
> Except if you are assingning one month property from another month property you have to check the documentation for both that they math (and not just almost match). Whereas with a strong type the compiler checks that for you.
Even if you have two type-matched "Month" properties, if they belong to dates in different timezones, there's a good chance you've just written a bug by forgetting to account for the difference - and that's a far more likely bug to slip through the cracks than mixing up month index and month ordinal. And when you don't have type-matched "Month" properties, the author will likely write the same code they would've with integers, just with a bunch more casts, if only because they don't want to eat the recompiles of touching a date/time header that gets included goodness knows how far and wide.
Putting it another way: I'd argue that the manipulation of individual months is already inherently not type safe, as it lacks enough context to be type safe.
So instead of documenting it in a single place, your argument is to document it in dozens or hundreds of places and somehow that'll never get out of date. Even though the single documentation in a single place must be assumed out of date?
Cool story bro.
> Oh gee, is that an `int` month property that states it's range? Yes. Yes it is. And I don't have to drill further into the documentation to look at the type of the property, and can just look at the property?
How do you know what is January? Is it 0? 1? Or something else entirely because someone thought it'd be cute to use the value of "JAN" since, after all, that 4 byte string perfectly fits in a 4 byte int.
> I have better things to do than to create or untangle a mess of opaque types that do little more than obfuscate the code
enum class Month : int {
January,
February,
...
}
Holy shit look at that obscene effort and obscurity! I should enter IOCC with that unreadable masterpiece.> won't actually add any safety in practice
`new Date(5, 10, 2020)`
Quick, what's the order of those arguments? Is it day month year? Or month day year like Americans like to do?When the documentation lives alongside the actual concrete use cases, it at least tends to get fixed when those individual concrete use cases are changed.
> How do you know what is January? Is it 0? 1? Or something else entirely because someone thought it'd be cute to use the value of "JAN" since, after all, that 4 byte string perfectly fits in a 4 byte int.
You didn't read the documentation I linked I see. "The month component, expressed as a value between 1 and 12." - this rules out fourcc nonsense, this rules out 0, this leaves only 1.
> Holy shit look at that obscene effort and obscurity!
0-based? Nice curveball number #1. Someone will probably refactor it to make it 1-based later, so that's nice curveball number #2 that will break all to/from int casts for interop. Or fix them - lets be honest, someone's going to cast (Month)some_var_that_is_1 for interop and expect January, and then probably cast back to (int)month for more interop, resuling in bugs canceling out bugs. I've also seen similar enums get accidentally reordered by someone fat-fingering Alt when pressing up-arrow, swapping lines, changing the values, for curveball #3... hope it gets caught in code review! It won't if such a typo sneaks into the initial version, but one can dream! So now I gotta read more than you could bother to type out...
> Quick, what's the order of those arguments?
Year month day. Easily verified by intellisense, too, since we're clearly okay with that in-thread. Your code will throw ArgumentOutOfRangeException. Someone will add these kinds of overloads for "convenience" even if you have a Month enum too BTW, so don't pretend that it's existence fixes things. I can totally get behind named arguments:
new Date(year: 2020, month: 10, day: 5)
Or "named" constructors: Date.FromYearMonthDay(2020, 10, 5)
Either of which I'd prefer over: new Date(new Year(2020), Month.October, new Day(5))
Programmer style YYYYMMDD isn't that bad either, honestly. But, even with that last one: What the heck is the timezone? UTC? Local timezone of the executing machine? Timezone of whatever machine is interpreting the Date instance? I'm gonna have to read the docs regardless...This whole made up scenario equally applies to ints but even worse. At least with the type I can find the usages. The Month type is at worst not better than the int type, but it's never worse which is the important part of being a guideline.
Your argument is basically boiling down to "the safer code is more error prone because I'll just suddenly be a way worse programmer for some reason whereas I can totally nail the dangerous code without any possibility of mistakes ever because... uh... just trust me or something"
> Year month day.
Says who? Remember documentation doesn't exist in your hypothetical argument world, which is why a type is somehow bad or something.
> Easily verified by intellisense, too
This is what intellisense will show me: `Date::Date(int, int, int)` That's not verifying fuck all. If we were using opaque types then intellisense would verify it for me.
> I can totally get behind named arguments:
Sure, is that your guideline? We don't need opaque types because we only ever used named arguments and return values don't exist?
And like below someone retorting below "hurr that's just bad code then" well all code is bad. Some people just seem to think their own machines will think for them or even worse, some other developer thought for them and didn't make any mistakes. Others realize people are infallible and you need installed hatches in order to get in there and fix things, and they prefer systems where it is easier to do so.
I feel like the big exception to this are very large libaries which can hire multiple SV-salaried engineers to work on them full-time (STL, Qt, etc), because yes, they likely might have done a good job. But some in some small shop C++ code, the constraints are different, the expected correctness is different, and you just can't trust that people have done everything right and it helps to have hatches.
Anyway, there are other issues with a Month type but other than that issue, the use of a const accessor method is yes better than public member, so I don't fully agree with them that the 1st guideline is that bad, I certainly agree with the spirit of it to a degree. I skimmed the guide and a lot of it is pretty good. Like C.2 really jives with me and gives me a very good rule that seems to explain why a lot of OOP code rubs me the wrong way, of which encapsulating a bunch of disparate state with no real invariants or constraints makes no sense but is something you see from a lot of code out there. That said, I still will never follow anything religiously, even if Bjarne Stroustrup wrote it because really no rules fit every situation and devs should be wary when people make pronouncements of rules that are total and fixed.
Sure, but the type (and thus the guideline) is still helping with that instead of hurting. I can much more easily find all usages of a broken `Month` type than I can an int representing a month which could be named anything.
> That said, I still will never follow anything religiously, even if Bjarne Stroustrup wrote it because really no rules fit every situation
Oh definitely, I don't agree/follow all of those guidelines either. The guidelines themselves don't pitch themselves as that, and many of the "rules" have whole sections listing reasonable exceptions and why those exceptions are OK.
I don't understand the downvotes. You're expressing your opinion nothing else.
That said, I generally agree with you. Layers of abstraction have a cognitive cost. That cost might be paid for by gains due to those abstractions, but the costs exist nonetheless. Whether this is bothersome is up to the programmer in question, but it is certainly a legitimate concern.
you don't necessarily have to read it. Many of the guidelines are implemented as checks in clang, for me the ones I find relevant and enabled show up inline when I write code in my IDE.
You're just admitting to having created your own problem, and suffering consequences for it. Nothing in your comment pertains any issue with reading coding guidelines in general or specifically the C++ ones, let alone any comment in particular.
> Now, the c++faq is obsolete.
That's your personal assertion, and one that does not sound informed at all.
I keep track of the C++ coding guidelines. In general they are nice rules of thumb. No one wants to build a religion around them.
The phenomenon literally has a wikipedia page describing it[0]. That's a lot of people to describe as "no one."
The guidelines document is:
- Self admittedly incomplete
- Self admittedly outdated
- Extremely long
- Not targeted at developers first, but at analysis tool writers [1]
- Raises the question (to me, at least): Why doesn't this document exist since the nineties? What was going on there? What took so long?
And you can use this set of bullet points to basically describe everything that CPP does and all its complexity. Compare it to Python's pep8 and it's shameful, even. They tried to write some guidelines and the language was so big and bloated that they couldn't complete the work, plus the result is so inaccessible that most developers will simply ignore it completely.
I work professionally with CPP right now and all these issues are constant sources of pain to me. I want to like the language, but everything is always like this.
[1] "We do not expect you to memorize all the rules before trying to write code. One way of thinking about these guidelines is as a specification for tools that happens to be readable by humans."
It is long because they work on adding examples to each one and specify precisely what they mean. Striving to reach the level of precision a pedantic C++ developer might demand when ambiguity arises.
This document didn't exist in the 90s because the way we shared information was fundamentally different back then, the web sucked. Also, many of these relate to more recently learned lessons or specific things about C++11 and it would be odd to mandate time travel for one document.
C++11 has existed for more than a decade, it has already been superseded by 3 newer versions of the language that add a lot of new features (some of them incompatible with each other), yet for most of its existence there was no official guideline from the creators that explains how it should (and shouldn't) be used. And as far as I know, nothing like it existed for C++98. It's too little, too late. They clearly have some priorities regarding documentation that, as a user, I strongly disagree with.
Big organizations (EA, Unreal, Google, etc) can live with this -- they can afford to have their own, very restrictive standards on how to use the language. Most small organizations (in my experience) don't, either through lack of expertise, money, time, or will (or a combination of all), and their code is usually ugly, hacky, unstable, and hard to work on. This document only solves this problem insofar it exists as a very strong recommendation to buy ReSharper and just do whatever it says.
Before I got to your last paragraph, I was like "but big companies manage it fine" and you are right that it isn't readily approachable for non-expert small teams. There are things like Jason Turner's starter repos for C++ but he makes assumptions and even he went the book route, and is non-official.
I think a lot of this comes down to age and size. C++ is astronomically large. I don't want these to be excuses, you raise legitimate questions that need to be addressed, but I do think every language will deal with this when getting this large.
C++ just formed differently than a lot of languages. Most languages have 1 canonical source or at least had 1 source before the modern era of package managers and docs bundled with the language. C++ existed a good 25 years before package managers for languages became popular, so even getting libraries is hard. But it does mean that there are many package managing system for C++. Many sources of docs, and many implementations. You have freedom to pick your own at the cost of needing to know how to make such choice.
Compare to Ruby; Yukihiro Mastumoto (Matz) made a Ruby interpreter, CRuby the community sometimes calls it. Then someone remade it on the JVM, JRuby. Then they, some nebulous they that were invested in Ruby, made a standard for the core language. Then Twitter, Rubinius, some C# dudes, all made their own, but the CRuby implementation was the reference so deviations were bugs. The stand exists, but at 1500 pages (language and core library) or something like that the language is underspecified so everyone looks to Matz for answers, and maybe the Spec is updated with his blessing. At some point he blessed RubyGems as the de facto package manager and even though other exist they are generally ignored.
In C++ everyone follows the standard, even Bjarne Stroustrop the creator doesn't have his own compiler anymore. When there are deviations from the Standard everyone knows the standard is official and can read up enough from a variety of public sources. There is no single source of truth like Matz for Ruby, but there is no bottleneck on technical details either. The C++ standard is also much more detailed, coming in at two 3k page docs, 1 for the language and another for std lib even though both are much smaller than Ruby. Because there have been so many technical arguments there is much greater specificity and thought put into details. All of this openness comes with vagaries and difficulty getting into it.
C++ isn't a turnkey solution for a specific domain, it is a very general purpose tool for experts. Nothing is stopping someone from packaging as a turnkey solution and some the big companies you mentioned do exactly that for their internal development.
* Form follows function.
* Minimalism principle, there's only one best way. (vs postmodern, there are many)
* Stanford, not New Jersey.
C++ is clearly a postmodern language, because instead of reducing competing features, it rather adds more and more variants of almost the same thing. Modernism means to reduce this syntax and library bloat.
I think you're both right. There are people that will interpret modern as temporal versus modern as in design.
I was referring to the temporal interpretation, and thinking in terms that either "modern cpp" becomes fixed in time to mean '>= c++11', or evolves to mean '>= c++23' and beyond.
In the former case, you'll have people unhappy because cpp style will surely continue to change, and people will complain that 'modern cpp' is the bad way. In the latter case, you'll have people complaining that "good style" is a shifting target, and feeling like they'll be accosted by trend setters no matter how they write their code.
In other circumstances I would agree, but cmake coined the term "modern cmake" when they released cmake 3.0 and with it their support for target-based projects, and after a couple of decades of "modern cmake" a sizeable portion of the cmake community still failed to update their code.
This renders it completely impractical -- sorry. You need to figure out a way to make it work, otherwise this is, errrm, useless.
And what is 'this' static analyzer anyway. I thought this is about a language extension introducing possible general static analyzers. Is this a concrete one now? Or why do you already know that it cannot handle '->'? Did I miss something? What is this?
I think this one is problematic too:
> Using the union keyword produces an error. … It was replaced by std::variant, which is safer.
Union is horribly unsafe, but at least union identifies its fields by name. Variant identifies its fields by position (unergonomic, error prone, and horrible for refactoring) or by type (unpleasant or useless when the type has no name or an unstable name, and even worse when two choices have the same type).
C++ needs an actual sum type, and variant isn’t it. Sorry.
Don't disagree at all, but in the meantime I'd still avoid union and use variant instead but wrapped in a class that exposes named accessors. You don't have to actually expose the orderings of a variant to anything that way.
It's a bunch of annoying boilerplate to be sure, though. If the metaclasses proposal ever happens then it could be generated which would be cool.
#include <boost/variant.hpp>
on a machine that was very fast for its time. It took seven seconds to compile! Now modern variadic templates are quite an improvement, but not enough of one. A metaclass generating an instantiation of an n-element variant which, in turn, instantiates the n-1 element tail, etc is quite a log of generated code and types, and anything using it needs to contend with all that complexity to resolve method calls, etc. In contrast, any language with native sum types can handle this efficiently.
Furthermore, native sum types make it obvious to the compiler, type checker, static analyzers, etc that the type in question is actually a sum type. Getting a compiler to understand a metaclass-based variant well enough to do something like Rust’s sentinel value optimizations sounds miserable.
The language spec has zero to do with this. You're commenting on a discussion on static code analyzers. The specific gotcha you're commenting on mentions that the overloaded operator -> from smart pointers returns a raw pointer to the object being managed, which apparently causes static code analyzers to lose context (it's a raw pointer). The gotcha just mentions a way to help out static code analyzers to not lose context. There's nothing wrong with the spec. At most there's a tip to help out current static code analyzers keep track of semantics.
The worst part of people criticizing C++ is that 9 times out of 10 they base their criticism on ignorance or misconceptions.
There are two obvious solutions here: either improve the proposal to make an exception for -> or improve the language so that -> is friendlier for static analyzers.
1. shared_ptr is very slow. It makes it safe to share data, but at the expense of many tiny allocations for each object type. C++'s memory manager isn't geared towards that kind of abuse, this is something that can really only be performant in a garbage-collected language where allocation is an atomic pointer increment and deallocation happens "later". Pushing people towards using this abstraction is wrong most of the time, it is often much better to use arena-style allocations for most cases in C++. I've seen libraries written in modern C++ where malloc and free account for 80% of the runtime. I don't see a way to fix this without radically altering the memory manager.
2. The impact on code compiled in debug mode is often unacceptable. I expect programs compiled with minimal to no optimisations to be slower, but layering all these abstractions usually produces a lot of runtime junk that gets in the way. For example, std::for_each can often be optimised to a regular for loop, which can then be vectorised, but in debug mode it is compiled to a function call which in turn calls the passed in lambda for each element and operator++ on the iterator. The difference in speed between the two is several orders of magnitude and makes this abstraction unacceptable for hot loops. This is a more minor gripe that can probably be solved by marking some functions as "function-like macros" similar in intent to LISP's macros, so the compiler knows they can be always inlined and optimised. GCC's -Og flag also helps a lot here. Still, the world isn't just GCC and the standard should probably bequeath more library functions with special status, like how memcpy &al can be always optimised to inlined machine code instead of a function call.
You'll notice both of those concern speed. If the loss of performance from said abstractions is not a deal-breaker for you, then you probably shouldn't be writing C++. D, C#, go, Java, maybe even JavaScript, Python or Lua would be a better choice for your project. The reason to use C++ for a new project is because you want to build really complex programs made up of operations that closely match the semantics of the machine on which they run, for the purpose of going very fast. Or you're stuck using C++ because you have to interface with C++.
I have spent lots of time thinking on all proposals that came up this year. And only one idea felt seriously better. No not the 'secure rust'. The idea behind Circle compiler described by author in one of podcasts I think. That compile time language should stop being ugly magic and should become normal full fledged programming language that can do everything.
Recently I have investigated some failures in builds that were happening only on one platform only in one branch only when specific change was applied. And I came to conclusion that gamedev does not have people who understand what C++ does at compile time. And to fix it we don't need more more magic in C++. So no, next C++ will not be C++ for us if we will have any other sane alternative. Most likely we are stuck with old C++ until new platforms are out.
Re >> "Software companies have a problem. There’s not enough candidates that can code C++. "
Should be written "There're not enough candidates who can code C++."
e.g.s:
"There is not enough talent" "There are not enough talented developers"
"There's" is not correct when used to refer to multiple things.
In every-day speak or in internet comments (Including here on HN), it doesn't bother me at all when the wrong form is used. Casual speak is casual. When I see it in an article, I assume that the author is ostensibly a writing professional; that's when the misuse really irks me. Doubly-so when it's in the opening statements. First impressions and all that....
That said, I had parents like that too, _and_ I did a comp-lit degree, so I'm basically hopeless.
I'd expect most people to either avoid the awkward contraction (probably most likely) or handle the plurality "incorrectly".
Similarly, D also is pushing for more static analysis to enforce safeties
I feel like D already is the next C++, it feels like C++ catching up on D, they went with the same syntax as D for modules, it's quite telling
safe rust is organized just like safe C++.
"Safe C++" you can segfault or mem leak in a single line. Good luck reading through a few dozen k lines of code of a 3rd party library to check if they adhere to every single convention of "safe C++".
You have articulated one of the biggest motivations I have for avoiding C++ in scientific computing. An R library that uses C++ to speed up a clustering algorithm that seemingly arbitrarily segfaults on my input is not the thing I should be having to debug.
I wonder how many C++ code bases never use v[x] on containers where it is undefined behaviour to go out of bounds? I've not seen one myself.
It's time consuming to start a class from scratch and professors prefer to reuse their material when they can, rather than learning/teaching something new. There may also be some inertia that makes it hard to change the core languages that are taught.
As for OCaml, there's a lot of academic research around functional languages, and professors doing research in PL are happy to teach OCaml/Haskell.
Not so much banks, but investment shops like hedge funds use OCaml e.g.
Jane Street Capital - https://www.janestreet.com/technology/
Oxford Knight - https://oxfordknight.co.uk/jobs/ocaml-hedge-fund/
whereas others don't (e.g. Goldman Sachs relies a lot on Slang, a homegrown thing).
That's the death knell. C++ will COBOL, not because it sucks (which I do think it kind of does due to feature overload and too much long history of different feature generations), but because it isn't even worth the money to learn.
It would definitely be very difficult to do Epochs in C++ 20. It got harder, and will continue to get harder. In my view, if it was possible in 2020, it won't be possible in 2026. Accordingly I think C++ is doomed. Get out while you still can (or, you know, size up lucrative consulting gigs as maintenance, it's not as though existing C++ code will all rust out [pun not intended] before you retire)
If you don't want comfortable, common features enshrined in the language, you can just write C. Personally, I think using C for anything complex is torture.
So "idiomatic C++" is a hilarious moving target, and the body of code (one of C++'s advantages) is a hodgepodge of idioms, feature usage, interfaces, etc.
That said, C++ is what it is, and it is kind of proud of it, so I say to C++, keep C++ing.
What's your plan to improve something that does not involve adding improvements?
No, it isn't. If anything, it's famous for its spartan standard library. It only received smart pointers in C++11 and a file system library in C++17.
> It desperately needs to be able to remove stuff but it can't because of its desire to maintain ABI compatibility with binaries compiled on older versions.
This is outright wrong. Some standards already deprecate and remove features, like std::auto_ptr going away in C++17 or volatile in C++20.
You're trying to pass off too many baseless assertions as undisputed facts.
There, I said it.
- Long build times
- Overly complex language
- Ugly syntax
Rust inherited a lot of problems C++ had. While making some progress in areas such as security is a huge success already, the language has a lot of potential to do more.
Those are not really priorities of Rust or most of its domains anyway.
> The long build times are a product of the type safety mechanisms built in.
The long builds times are a product of the crate culture. Like JavaScript, many people are happy to pull in crates that are tiny to avoid having to write anything themself. So you end up with many packages pulling in over 100 crates.
The Rust compiler team is working on speeding up build times as we speak. Give it some time. If a language was perfect in other respects (e.g. free of memory leaks), I won't mind waiting a little longer for my builds, but I know this bothers many. In any case, it's being looked into.
The complexity argument I agree. Rust won't be the last language for that reason. Already it influences languages like Zig and Jakt, who aspire to be simpler and still suitable for systems programming.
I disagree re: ugly syntax, but that's a matter of taste.
Build times in C++ projects nowadays are mainly the result of naive approaches to project structure and total lack of care towards modularity. On top of that we also have helpers such as ccache and the like to further reduce build times by not having to build at all.
* Rust has a package manager.
* Rust has macros.
* Rust makes it relatively simple to choose between compile-time and runtime polymorphism.
* Rust's traits can be more flexible than C++'s classes.
Rust feels like a modern language. Modern C++ feels like it's struggling under the massive weight of decades of cruft.
* While not perfect, C++ has a few package manager that are quite solid (personal favorite being conan).
* What can Rust macros do that C++ TMP (and macros even though that's a last resort IMHO...) can't do?
* From what I've gathered, it's similar in complexity to using the CRTP in C++, maybe a bit less verbose. Am I missing something?
* I feel like "interfaces" as in pure abstract virtual classes can achieve the same effect as Rust traits (though it could be its own thing and not a special case of a class specification), and the newly introduced Concepts goes even further that what can be achieved.
Don't get me wrong, I like the improvements rust brought, especially in terms of safety, but other than that... I don't think C++ is particularly far, or even actually behind for some of these.
A better comparison to Rust macros might be constexpr and template meta-programming.
I'm not sure why you specifically compare Rust macros with C++ TMP. Rust macros are meant to be a safer and more flexible alternative to C/C++ macros, not else. C++ TMP is not a goal but a means to the goal, so if you look for something like TMP in Rust you will see nothing (the closest is generics, but of course it doesn't subsume TMP because it needn't). The goal of TMP is generically a compile-time metaprogramming and Rust has multiple solutions for this, including but not limited to generics, Cargo-directed code generation (via `build.rs`), macros by examples (`macro_rules!`) and procedural macros.
> From what I've gathered, it's similar in complexity to using the CRTP in C++, maybe a bit less verbose. Am I missing something?
CRTP (curiously recurring template pattern) is really aptly named and it is a testament to C++'s accidental complexity. I don't think any new C++ user can discover CRTP in reasonable time. In Rust traits are built-in and well incorporated into the core language.
> I feel like "interfaces" as in pure abstract virtual classes can achieve the same effect as Rust traits (though it could be its own thing and not a special case of a class specification), and the newly introduced Concepts goes even further that what can be achieved.
You have a generally right idea about traits that it is conceptually similar to concepts, but I believe there is currently no equivalent of `dyn Trait` for concepts. There was no effort to harmonize concepts and existing virtual classes.
Also your parenthesized remark is more important than you imagine; `dyn Trait` in Rust is implemented as a fat pointer, which decouples vtables from instances and is generally much less complex than C++ vtables which have to account for multiple inheritance for example. As a C++ programmer you may feel like that you want a choice between vtable and virtual methods vs. no vtable and no virtual methods, but it is a false dichotomy and Rust gives the best of both "choices".
I feel like the problem is that it isn't just a package manager... it insists on simultaneously being not only "a" but "the" build tooling, which is trading way too much flexibility for a very small amount of convenience. And like, OK: maybe this is useful for people who are just getting started, but I would firmly argue that this kind of tooling should consider the needs of the expert and not make severe tradeoffs for the needs of the beginner unless it is trivially and obviously optional and there can be growth through changes in tooling (where maybe people "graduate" from Cargo to a more extensible tool... but since it is also the package manager, that's brutal).
The issues for the tool are then riddled with bugs that have been open for years (I think some of the ones that I've been blocking on might already be at 5 years, which is shocking to me as I don't even think of it being very old) that even often have multiple reasonable pull requests filed but where no progress ever gets made on fixing the problems because, even though it is often trivially shown that there is no way to use the tools correctly currently in the general case, they insist that the behavior of a working tool might break some theoretical user relying on the current inconsistent set of behaviors.
The result of all of this is that there are a ton of workarounds for Cargo that projects have started to have to embed almost as boilerplate into their projects, including a popular one that is going around right now (required for Linux->Linux cross) involving setting an environment variable that explicitly tells the user not to set it ever in the name of the variable but like... Cargo doesn't be have any other way of making this stuff work correctly. It is honestly infuriating and ridiculous and there are good reason why you keep seeing stuff like Google avoiding it entirely as part of their Bazel integration.
> setting an environment variable that explicitly tells the user not to set it ever in the name of the variable but like... Cargo doesn't be have any other way of making this stuff work correctly.
This has nothing to do with Cargo, if you're talking about RUSTC_BOOTSTRAP. This is because the kernel would like to use a stable compiler, but unstable features. This isn't a supported build configuration; the environment variable is an escape hatch for the bootstrap process, and isn't intended for nothing more. It would be, imho, unreasonable to expect Cargo to support something that is explicitly not intended to be supported.
I'm also not fond of making the package manager also kinda the build system, but that's just my preference to keep it flexible.
Linking to a library from another language is rather considered an 'edge/complex case' in most circumstances outside of systems programming and the lib having a C API. And that case is literally three lines of Rust in build.rs if you use e.g. bindgen.
While Rust is a systems programming language the primary use cases, judging from the ecosystem of crates, are actually /not/ drivers or desktop apps. The latter are an 'edge/complex case' that just happens to be also nicely covered.
And not doing that (i.e. operating solely inside the language ecosystem) is what most users of such ecosystem would reserve the term 'basic' for.
To understand where I'm coming from: would you consider 'linking' to a Python or Erlang libray from C++ 'basic' or an 'edged/complex case'?
* do not exist yet
* struggle to provide full bindings
* do not keep up
* have stopped being updated
One of the most glaring example for me is Assimp, which still does not have its Rust counterpart (and unsurprisingly, because Assimp has a weird interface to begin with).
I didn't stumble upon bindgen, but from what I gather it's for library developers, not consumers, right?
> the primary use cases, judging from the ecosystem of crates, are actually /not/ drivers or desktop apps
Well, in that case (but not aiming specifically at you) why do I see so much claims that "Rust is the new C++"?
> would you consider 'linking' to a Python or Erlang libray from C++ 'basic' or an 'edged/complex case'?
I would consider it complex, but this is wildly different IMHO than Rust with C or C++ (but mostly C to keep it sane), which, while more "complex" than pure Rust, still falls within the reasonable expectations. The fact that there are so many crates wrapping around C (and C++ to some extent) libraries means that for the most part, that "complexity" has been offset by crates developers, but many Rust use-cases still seem to require a lot of C/C++ code to even exist (even if abstracted away by glue crates).
Rust has two kinds of macro with very different properties, so lets talk about that briefly (reader: if you are here to learn about this C++ Proposal paper then er, bye?)
1. The macro_rules! "By example" macros. These are a bit like the simpler C macros you might have used, except that they really care about syntax and aren't just gluing text together without understanding it. These macros are hygienic (if a macro uses its own variable named "count" and your function has a variable named "count" Rust knows those are different variables and everything works fine whereas with a C or C++ macro that will cause trouble) and they're fairly powerful for solving those annoying "Don't Repeat Yourself" problems in programming when you can't quite factor out the repetition in the Rust code per se.
These macros are very safe, I would trust a novice to write and use them, worst case maybe you need to show the novice how to ask Rust to apply the macro and show the results so they can see what they screwed up, because e.g. "suck it and see" doesn't work very well to figure out what you did wrong writing a macro.
2. Procedural ("proc") macros. There are several specific flavours of proc macro but they're all Rust code, which you wrote, running inside the compiler during the build, taking bits of the program syntax tree as input parameters.
This can do anything. For example Mara wrote whichever_compiles! a proc macro which forks the compiler, attempting each of the options you supplied, once one of them compiles the other compilers die and the survivor presses on compiling the rest of your program. It's deeply regrettable that this is possible, but it is. Only experts should touch proc macros, and they should obviously not do anything crazy unless (like Mara) they are doing so as a joke.
So, proc macros can do a lot more than C++. Perhaps more than they should do, but more.
> I feel like "interfaces" as in pure abstract virtual classes can achieve the same effect as Rust traits
C++ traits are roughly in the same space as C++ 0x Concepts. You might think aha, we have C++ 20 Concepts. Unfortunately C++ 20 Concepts is a pale shadow.
Traits does a few important things (possible to some extent in C++ 0x Concepts) which C++ 20 Concepts can't do. In particular, it has semantic weight because the concepts must be explicitly implemented. In C++ two concepts with the same syntactic requirement are functionally identical. The ISO document says using a concept which is not correctly "modelled" means your program is ill-defined (might do anything) but in practice the code compiles and everything seems OK. In Rust even though there's no syntactic difference between PartialEq and Eq, the types which don't explicitly implement Eq are not Eq. That is, you can programmatically ask, "Is a == b an equivalence relation?" whereas in C++ all you can actually ask is "Can I write a == b ?"
More directly practically, Rust actually checks Traits whereas in C++ there is no checking.
e.g. if you try to sum() a Vec of u32s that will work, but if you try to do that to a Vec of Strings there is no such function, the Sum Trait isn't implemented for Strings.
You could do this in C++ but it's much easier not to, and so most software doesn't and then your users are fed a lot of SFINAE compiler errors which choke them to death. In Rust it's mandatory to spell out the required Traits.
Most of Rust's traits are safe, which means you can't get them wrong but then on the other hand I can't rely on their meaning beyond syntax. Unsafe traits (which need the unsafe keyword to implement) can make promises and if they break the promise that's Undefined Behaviour.
For example Rust's iterators provide a hint about how big they are. But the TrustedLen unsafe trait on an iterator says its hint isn't just a hint, this actually promises it is true, if it's not true then it's OK if the program crashes. So for example an array's provided iterator is TrustedLen, the array of six things knows it has six things in it, that's what an array of six things is, duh. So code using that iterator can correctly emit code to handle exactly six things, and it will work. Whereas my GoogleSearcher HTTP API might hint it has six things, but then actually Google only returned four, because Google, it isn't TrustedLen, so code might optimise for six based on the hint, but it mustn't crash because I didn't promise six it was only a hint. Emitting code that blows up if there aren't exactly six items is a bug.
Please don't say rust++, I'm curious as to what the next thing will look like. Maybe I have to a bit longer
rust.iter().next();But people will need some time to create that.
I believe much of C++'s complexity does not come from its C heritage, but rather from having defined the primitives to control memory usage, object lifetime, and layout.
But yes, to the extent that C++ still had some niche before Rust matured, that last niche doesn't exist anymore. I disagree even that Rust was the largest C++ killer, but it seems to really be the last one.
Incidentally this is why there's basically no Rust in the Google monorepo. Rust might be slightly better than C++, but it's not a lot better, and bifurcating the entire ecosystem for something that is marginally better isn't worth it.
Go inherits from Oberon through Robert Griesemer, and from C through Ken Thompson (father of Unix) and Rob Pike (Bell Labs).
I don't bother with C anymore, except in small parts of code where every cycle counts. Just about anything you write in C can be mechanically translated to Go.
C++ shows us what NOT to do. I've used C++ for most of my professional programming career and it's simply an impediment. Huge waste of time and energy.
Rust is the successor to C.
If anything builds upon C++, it would be Rust.
Rust still has structs with methods kind of OOP, but you can also have that in C using structs with function pointers. So, not really OOP.
Let it die already.
A language can offer features which support this style well, or not, and you can insist on programming this way regardless of whether the features support it well.
In particular Multiple Inheritance is the sort of thing OO languages have (C++ and Python for example) which I am confident is a mistake. If your OO model requires multiple inheritance the model is wrong, go re-design it.
There are effectively still classes, methods, members, interfaces, with all the syntactic sugar that goes along with it.
https://doc.rust-lang.org/book/ch17-00-oop.html
It feels like this whole discussion about successors and whatnot is just pointless semantics. Rust fits into a lot of C and C++ use cases and is a good tool to have in your toolbox. C still has its place too.
I highly disagree. It is not "pointless semantics". To think everything in the world is some tool in a toolbox and nothing is truly ever bad and everything has a use is naive. Some things are good some things are bad and some things are OOP while other things are not.
Rust is not focusing on the OOP philosophy. Both GO and Rust are moving away from that style of abstraction in terms of philosophical goals. If you want to twist the language into OOP that's still possible and still your prerogative. But to deny that the change in philosophy doesn't exist because it's "possible" to do OOP in rust is basically denying the existence of something that CLEARLY exists.
I'm not saying OOP is bad. I'm saying that this is the current status quo. My opinion on the OOP style was not mentioned at all.
It's true that a lot of the OOP ideas went into C++, but C++ has always been fairly agnostic to whether you actually use those features. Rust just seems to have taken that a little bit further.
The closest to an exception is inheritance, but it's been months since I've built an inheritance hierachy that wasn't just an interface that could be done with traits. When it comes up I happily use inheritance to do it and if I was writing Rust I would moan a bit and come up with an alternative design, but I'd hardly call it a big deal.
It seems like most languages built today have a thick vein of OOP in their design (discounting functional etc). You don't have to be 100% or 0%, Rust is way closer to OOP languages than it is to C.
Interfaces are part of type theory. Generics so to say.
The unique thing about OOP is objects with mutating state that communicate with one another. This is really the only part of OOP that is uniquely OOP. All other things that are "OOP" like interfaces or object.method() notation are not intrinsic to oop.
Rust moves away from this paradigm I describe.
In what way does C++ have this but Rust doesn't?
You should be asking the question in what way does OOP mutate state and what other programming paradigms don't? What described above is unique to the oop style.
In general when you write rust. Unless you love oop, in general the syntax isn't set up to promote you creating a graph of objects that mutate one another. Java promotes this style the most.
For rust the style the syntax promotes is movement of data through pipelines. The data can get manipulated as it moves through the pipeline.
For functional programming it's similar. You have pipelines, but nothing is being manipulated. Each segment of the pipe in this case takes an input and produces a new output without moving or changing anything.
OOP has been around long enough that all the good ideas have been borrowed/copied/reinterpretted/stolen many times over. Any ideas that managed to remain unique to OOP all this time must have sucked.
Setters are not used in any other programming paradigm. Not in FP, not in C style programming... Any style of programming that strictly is not OOP does not use setters.
Better brush up on OOPSLA, ACM and IEEE literature.
Interfaces exist in functional languages as well. You need to search for the thing that is uniquely oop. Interfaces is not it. Clearly this is an inconsistency.
If IEEE defines interfaces as oop then it is also saying functional programming is oop. This doesn't mesh with our intuition of the meaning of these two terms.
There is a differentiator that is uniquely oop and uniquely functional and that is mutation via setters vs. immutability. Functional programs must be immutable, while oop allows for mutation via setters.
Some people like to roll with the notion that oop and functional are orthogonal concepts, this is an inconsistent notion. You have to realize that mutation via setters definitively makes the two concepts parallel and opposite. Functional cannot mutate.
If the industry ever truly mathematically formalized the term oop rather then relying on crude improvised guidelines via IEEE then mutation via setters is the formal definition of oop.
Everyone nowadays gets confused because lack of proper formalism. The think if they used a method like a getter or inheritance or a class, then they did oop. When this is the case, suddenly everything looks like oop. Suddenly oop is prevalent and the dominant paradigm and used everywhere.
Some of the initial implementation of interfaces go back to CLU ADTs, Smalltalk traits, Objective-C Protocols, Java interfaces, C++ pure virtual classes, BETA patterns, Eiffel, CLOS protocols....
You don't even know who wrote the IEEE definition and who validated it. Second this isn't academic truth. This is just some arbitrary chosen standard chosen by industry. Not academic at all.
In mathematics, the ultimate logical form of academia there is a term for "interfaces". It's called categories, and there's a entire theoretical framework behind it called "Category Theory". This theory of "interfaces" goes beyond just OOP and category theory can be used to foundation-ally describe all of mathematics and function as a sort of replacement of set theory.
Don't worship authority for authorities sake. If the IEEE declared 1+1=3 would you believe it? Instead use your own brain. Do my arguments have merit? Does the IEEE definition seem inconsistent? Does my definition make logical sense? If you can think of these things on your own merits instead of blindly following the IEEE then you'll see that the definition of OOP is just something academia hasn't really thought about deeply from a theoretical and mathematical standpoint. OOP is more like a term that comes from applied engineering with very little theoretical foundation.
If you want some sort of authority I googled some authoritative talk about category theory explained to programmers: https://www.youtube.com/watch?v=JMP6gI5mLHc
I'm sure maybe you heard of haskell and category theory? Haskell is a purely functional language and it has huge connections with category theory. Basically Haskell IS the language of interfaces. It's entire type system is centered around categories or AKA "interfaces". It is the defacto language of interfaces much more-so then OOP languages like Java.
If you said haskell was OOP, people who love that language would vomit. Haskell is not OOP, but it has interfaces, so the obvious conclusion here is "interfaces" ARE not an OOP exclusive concept. It's therefore bad for a formal definitio of OOP.
So now the question is WHAT is it about OOP that makes it uniquely OOP. Or is it just a hodgepodge of random stuff and it will be forever a word that is muddy, inconsistent and poorly defined?
By the way, type classes are somehow related to OOP, maybe you should listen to a guy called Simon Peyton Jones on Haskell influences.
To save you some searching effort, https://www.slideshare.net/nushio/peyton-jones2011type-class...
I would advise to watch the talk that goes with the slides.
OOP is not formally agreed upon in academia there is no consensus. So your reliance on academia here is flawed. Use your own brain rather then blindly trusting others. You know about the replication crisis right? Science is found to be wrong over 50% of the time especially in fields like psychology. Don't be blind.
Please elucidate the crowd on the academic value of your research to computing progress.
Apologies for not understanding this - if the delineation between OOP and not OOP is so clear, it should be easy to come up with an objective criterion for being OOP that C++ satisfies and Rust doesn't satisfy.
I'm not sure what that would be. Inheritance?
If that can't be done, then I'd say it's a matter of opinion.
A venn diagram.
The left is all other programming paradigms. The right is oop. Most of the oop stuff is in the intersection of these circles. What's to the right is encapsulated mutation. Getters and setters and variants of these two concepts. Mostly setters though.
All of oop is simply instantiated objects manipulating and communicating with each other with getters and setters. This is unique to oop. You can't even imitate this with a functional language.
But it shouldn't be functional vs. oop. You can avoid writing any getters and setters in your program and while you wouldn't be doing oop, you wouldn't be doing functional either.
mutate_function(a) is isomorphic to a.mutate_method(). The latter is oop the former is not. There is a design tradeoff here that's actually well known and named. I forgot what it was called though. But also note how I used mutate here in the names. It's because this property of mutation is the main differentiator that causes oop to be different.
If everything was immutable but you still used methods everywhere it wouldn't be uniquely oop. It would be functional as well. And if you think about it is the very property of mutation that allows objects to communicate and mutate one another which fits with our intuition of what oop is. That's why methods that mutate should formally define oop and what it actually is.
I have object a and I have object b. A is similar to b. How do I define a in terms of b? That is inheritance. Functional languages can support this type of syntactic sugar. It is not exclusive to OOP.
std::args().skip(1).next().unwrap();
...to be OOP? Because that looks remarkably like some Java code I've written.What I mean by OOP is the smallest unit of programming being an Object with mutating data. These objects are basically combinations of mutable data and methods tied together into entities. Imagine a graph of a bunch of mini-programs with mutating state, moving around, getting injected into one another and talking to one another. This is OOP.
This is opposed to another style of programming where data flows through pipelines from input to output rather then a bunch of entities messaging each other and changing each others state. Both Rust and Go are moving away from this OOP paradigm of programming by putting less emphasis on OOP based syntax.
Two of the most popular are what I've nicknamed the "Smalltalk definition" and the "Java definition".
Alan Kay, on OOP:
> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP.
Java, which I attribute to Java simply because it's very popular and clear about how they think about this:
> https://docs.oracle.com/javase/tutorial/java/concepts/ defines five core concepts:
> * An object is a software bundle of related state and behavior.
> * A class is a blueprint or prototype from which objects are created.
> * Inheritance provides a powerful and natural mechanism for organizing and structuring your software.
> * An interface is a contract between a class and the outside world.
> * A package is a namespace for organizing classes and interfaces in a logical manner.
This Rust code is missing key aspects of both definitions. The surface syntax may look the same, but the details are different:
vs Smalltalk, this is the opposite of late bound, or "messaging": it's all early bound, aka statically dispatched.
vs Java, there's no objects here: while there is a method syntax, Rust separates state and behavior, into structs and functions. Methods are functions that support the method call syntax in addition to the regular function syntax. There's no classes here, nor inheritance. There is the use of an interface-like thing, and namespace/package like things.
TL;DR: there's more to OOP than method syntax.
Carbon is the successor to C++, according to G and creators
Go might be a good language even if I prefer otherwise, but the next C it is not, at least not yet.
I'm not much of a runner, you want to catch those goalposts before they get away?
edit: typo
I different goals than them.
Rust fits into a lot of C and C++ use cases, saying it's the successor of one or the other doesn't seem all that coherent.
Rust is one of the most complex languages I have worked with (most of that complexity could have been avoided with a better language design).
So no, I don't consider them related in any ways.
C is simple compared to others, however it is far from "extremely simple".
The language i'd call extremely simple (while still being usable) is Wirth's Oberon-07[0]. Note the "-07" part, there have been a bunch of "Oberons", many which add a ton of additional stuff, not all Oberons are the same or even can be called simple. Oberon-07 however was designed to be simple while also being usable to create a full GUI OS on a custom architecture[1].
[0] http://people.inf.ethz.ch/wirth/Oberon/Oberon07.Report.pdf
Well, I suppose the standard library is actually, well, standard, instead of a de facto standard.
Do you have material I can read about this? Perhaps some of this can be incorporated into future editions of Rust to improve ergonomics.
> C is an extremely simple language.
It could be, however considering a language complexity in isolation is misleading. C shifts the memory management correctness to the user, which adds significant operational complexity.
Every once in a while somebody writes that 6502 assembly is extremely simple. Well, yeah, but try to do a division.
> most of that complexity could have been avoided with a better language design
Can you elaborate the specifics? I do program in Rust, and most of the complexity is due to two factors - memory management (which causes a significant proliferation of types, for starters) and low-level control (which requires extra complexity; stupid example: stack vs. heap allocation). None of them can be avoided, without shifting the level of the language (which is certainly fair; if one doesn't need lower level control, there are other languages which can be more developer-efficient).
Are you saying that the ownership/borrowing model for memory safety was a poor choice, or that it's possible to make a language on that model that has vastly less complexity than Rust?
Because your phrasing implies the second, but I don't think I've ever heard anyone make that claim before (presumably because there's no such language lying around anywhere, so it seems hard to defend).
If the first, then I'd point out that wouldn't be a language design flaw, but rather a flawed goal (ie a goal that proved too complex to achieve) - their goal was a memory safe language that didn't rely on a GC, and the resultant design complexity is basically the best example of a way to solve for that goal so far. I'm sure there will eventually be simpler options, but I don't think you can blame designers for not having access to undiscovered techniques. That's what most anti-Rust arguements boil down to.
If you just mean you don't like the syntax, then yeah, I agree, it's a bit alien for me too, but I hardly concider that anything beyond personal taste and the difficulty of learning something new.
For example there are multiple syntaxes for adding annotations or directives to the code. Some things are written very differently compared to other languages for no apparent reasons (e.g. derive). Some keywords have different meaning in different contexts (e.g self and type). And so on...
Basically, there are some rough edges that shouldn't really be there
Bitwig (the DAW) has a Java GUI but the core engine is C++ iirc.
Garbage collection becomes almost a necessity when you have enormous concurrency and parallelism. I wonder whether you have experience writing such programs with pthread and malloc/free?
No, you can just use Rust, which provides thread safety without garbage collection.
C++ has libraries threadsafe shared pointers, interthread message passing, transactional memory, futures/promises, work stealing queues, and access to all the OS primitives to build anything else.
Go, on the other hand, does have scaling issues. So do most GC'd languages in fact, as the GCs want to periodically park all threads to walk their stacks. Even the latest & greatest GCs, like Shenandoah, still gotta park all them threads periodically for milliseconds at a time.
But the layer being abstracted by the language is totally different. Go is a managed runtime which assumes the use of a threadsafe heap on a multicore host, it provides a stack switching engine for concurrency management and a scheduler on top of that, and assumes the use of robust I/O primitives with asynchronous capabilities.
C gives you an easy way to express access basic CPU operations on top of a flat memory model, and maps well to the lowest level of OS system calls. And that's about it. It has some simple library stuff on top that gives you bits and pieces of what you get with big managed languages, but in general everyone recognizes that this isn't its strength and that you shouldn't use it for those problems.
But the flip side is that if you have problems that don't map to managed environments well, C remains about as good an environment as you're going to find. If you're writing kernels and drivers and firmware, you aren't going to be able to use Go.
It also has a garbage collector, which while fine for most use cases is not acceptable for some use cases that C/C++ (and now Rust) are being used for.