C++ Is Getting A Borrow Checker

C++ is evolving to incorporate features from every language, but with all these changes, are we just programming in C++ no matter what?

All right, here we go! We got C++ and Crab Lang—it's going to happen. Let's do this! At what point does C++ become every language? When does C++ get currying in the syntax of OCaml? At that point, could one argue you're always programming in C++? When it adds every feature from every language, every language, every feature—C++—you're always technically programming in C++.

Pattern matching—oh my goodness, pattern matching! Soon, really? Does this make it C++? Yeah, I've been programming C++. Look at this C++ going right here! Look at how good this is. That's C++! C++ will soon incorporate what I like to call the Turo 2—uh, light ship bad guy operator. It's a mouthful; some people call it the Waller operator. This guy right here—instead of doing auto, you can just now do that operator, and it will just do auto for you. It's pretty great!

C++ is going to take your wi, so you got to hide them—your children! The TAC Tu—yeah, it is TAC Tu. Soon, C++ 2—oh damn! If C++ is so good, then why not make C++ 2? Too bad! It's already on to C++ 23. Okay, they've made so many updates. I don't know if it's because of the Rust Lobby or European Union regulations, but C++ is about to become a safe language. The C classic GDPR—is that what's happening right now? The European Union once again is regulating my American freedoms!

The C++ Community has published a proposal for a safe C++ that would allow us to write safer code and spoil all the fun you can have with the latest C++ 98 standard. This proposal basically tries to bring... okay, so just in case anyone's wondering, when it says the latest standard 98, you guys are all like, "Well dude, that's so old!" There are plenty of new versions of C++, like C++ 11, 13, 17, 20, 23. The thing is, though, all these standards do come out, but every company you ever go to work for is still on 98. So it still hurts a little bit, or they're on that pre-C++ 11. You know how C++ 11 didn't just... it wasn't just C++ 11; there are C++ 11 Part One and then C++—like Return of the Jedi—and no one's on Return of the Jedi. None of them! They're all on A New Hope, and it's just emotionally painful. It just entirely skips The Empire Strikes Back—it's just awful!

Safety features from Rust to C++—but why not just rewrite everything in Rust then? Or why not write all new code in Rust? It can interoperate with C++, right? By the way, I know we're barely into this, but I saw this tweet this morning, and I just wanted to re-highlight it due to how amazing it was. Let's find it! We're scrolling, we're scrolling... it should be right here. This is, by the way, if you don't know who Justin Keys is. Justin Keys is the guy who forked Vim to make Neovim. He is the benevolent dictator for life of Neovim, so if you want to know who he is—like, genuine badass, right?

They have decided not to do anything with Rust but have actually decided to go with Zig. Look at this! Even inside their pull requests or even inside their issues, they're playing around right here. They're doing this whole little Zig building Wonderland right now, where they're actually building and doing stuff in Zig as opposed to that. I love what he says: Neovim artfully avoided the rewriting Rust catfish, but instead, they're just waiting for Zig. Now that, my friends, that's W material right there! That's W material! Again, Justin, why do you think he's the benevolent dictator for life of Neovim? Lua—good choice, Justin! You know, a lot of people probably would have gone with JavaScript. Oh, that would have been fun! Imagine actually trying to integrate V8 into your project—that would have been a full-time job for a year plus. That would have been fun! No, instead you chose Lua, which took you a day, and then it took you about a year to make sure all the APIs are correct and coherent and for the most part match the old Vim style. Blessings, Justin! Blessings! You focus on the good problems. Always did the right thing—Tree sitter, LSP client, Lua—the boy's a genius!

Okay, the boy is an actual genius. Sorry for the distraction there. You know I am out here, I'm just in love! Okay, I'm in love! Well, it's not that simple, and the proposal also acknowledges this point. Basically, there is way too much code to be rewritten, and the problem with interoperability is that Rust semantics differ quite a lot from C++, which affects the way the code is structured.

=> 00:05:26

Choosing the right programming language is just the beginning; mastering its nuances takes time and dedication.

I chose Lua, which took me a day, and then it took me about a year to make sure all the APIs are correct and coherent. For the most part, they match the old Vim style. Blessings to Justin for focusing on the good problems; he always did the right thing. Tree sitter, LSP client, Lua—the boy's a genius. Okay, the boy is an actual genius. Sorry for the distraction; you know I am just in love—okay, I'm in love.

Well, it's not that simple, and the proposal also acknowledges this point. Basically, there is way too much code to be rewritten. The problem with interrupt is that Rust semantics differ quite a lot from C++, which affects the way the code is structured. This would also require a developer that's older than 45. We got to go backwards a little bit and rewrite it in a slightly different C++, yet completely incompatible. That's funny; I'm loving this. Okay, I feel attacked. You should be a C now C++ developer.

This is very, very good. By the way, this is really good. It’s quite reasonable to try to bring safety into C++. I thought again about The Return of the JediC++ 11 part two. I thought that one should really be called Empire Strikes Back; it just doesn't feel good. Return of those safety comes with smart pointers. Smart pointers are like early version Arc and Rc's, and they give you a lot of the safety that you need. With a unique pointer, you effectively have a bunch of compile-time and runtime guarantees. With shared pointers, you have an atomic int, and you ensure... yeah, smart pointers add some overhead for sure.

For sure, Ray is an early version of safety. Since we’re on this Star Wars reference, Ray is a character that nobody likes. So, you saying Ray and me doing the Star Wars things brings up a mixed bag of emotions because we like Ray and C++, but not in Star Wars, so it's hard. Okay, be a man, do it raw. Unique pointers do not add any overhead; shared pointers do exactly because shared pointers require an atomic reference. They’re actually an Arc, whereas unique pointers are, I believe, all compile-time semantics.

That isn’t the only such effort in recent years, though. Google, for example, has been working on Carbon for a while now. I plan on taking a look at it sometime soon too. Anyway, straight to the proposal: here are some properties that safe C++ tries to deliver. It is a superset of C++ with a safe subset. Undefined behavior is prohibited from originating in the safe subset. The safe and unsafe parts of the language are clearly delineated; users must explicitly leave the safe context to write unsafe operations.

Oh, it's kind of like the inverse of Rust. You'll actually have a safe keyword in which you now have to operate in the safe world. I actually like that; I really, really do. I think Zig does it really well because you can use options if you want to, or you can use any opaque and void star that crap. You can also allow NS; you get to kind of opt in. You get to choose where your safety levels are.

Safe is such a troll. Well, I mean safe in the sense that you can't do the same level of memory things. When people say safe, this is the pro. By the way, this is actually one of my big gripes with Rust: people who write Rust keep saying the same stupid argument over and over again: "If it compiles, it's going to work." It's like, no, no, it's not going to work. Okay, you're still a shitty programmer, and you still produce shitty code. Rust isn't going to save you from that.

Rust has never saved anyone from being a bad programmer. Safe simply means you're not going to accidentally smash your stacks. That's what that means. Logic errors will always exist. I've created memory leaks in Rust because I forgot to delete keys in a hashmap. Like, that's normal; that's a normal thing. You have skill issues. I don't honestly think Rust would do a lot better in the public eye if they dropped that stupid mantra that everyone responds to, which is, "Oh, if it compiles, it's probably going to work." It's not a good mantra. You still need proper testing for runtime actions.

Yeah, because there's that whole program side. It's not stupid; it's literally true. It's literally true. Alright, let's keep going. The safe subset must remain useful.

=> 00:09:43

Even the safest programming languages can't eliminate all errors; proper testing is still essential.

Logic errors will always exist. I’ve created memory leaks in Rust; why? Because I forgot to delete keys in a hashmap. Like, that’s normal—that’s a normal thing. You have skill issues. I honestly think Rust would do a lot better in the public eye if they dropped that stupid mantra that everyone responds to, which is, “Oh, if it compiles, it’s probably going to work.” It’s not a good mantra; you still need proper testing for runtime actions.

There’s that whole program side; it’s not stupid. It’s literally true. All right, let’s keep going. The safe subset must remain useful. If we get rid of a crucial unsafe technology like unions and pointers, we should supply a safe alternative like choice types and borrows. A safe toolchain is not useful if it’s so inexpressive that you can’t get your work done. Then, that’s truly the safest way. The safest programming language is the language in which you cannot add any more lines to. It’s the safest—it’s the safest code, the one you can add to.

I know C unions, so I would assume you’d get tag unions, right? Because tag unions are more safe because you can’t misinterpret what it is. Is that, I assume, they call them enums, right? Enumeration—whatever you want to call it, tagged unions. I assume this just tag unions are considered safe. Yeah, STD variant—whatever it’s called.

The new system can’t break existing code. If you point a safe C++ compiler at existing C++ code, that code must compile normally. Users opt into the new safety mechanisms. Safe C++ is an extension of C++. It adds a robust safety model, but it’s not a new language. Safe C++ will be available under a feature flag, which makes sure the change is backwards compatible with older code. As everything is opt-in, this then enables a bunch of safety checks.

The way it works is that it lowers the function definitions to the mid-level intermediate representation, or M for short, and it performs borrow checking on it exactly the same as Rust. We’ll then be able to use the safe keyword after a function. By the way, when did it become “Funk”? Have I been out of C++ so long that “funk” is now a keyword for function? Oh yeah, sorry, you can’t.

Okay, first off, you can never name a function “Funk” because my gob brain cannot see it. I literally could not see it as anything but a function definition, and so I’m trying to figure out how this works. Dude, cooked for sure. Yeah, dude, I did just entirely too much going right now—entirely, dude, kich eating, bro. I’ve been kich eating.

All right, anyways, okay, so it’s a function named Funk—terrible name, by the way—that returns an INT and is safe. Then you can do unsafe operations. Oh, okay, so “safe” is a modifier on a scope, I think that’s correct. So you can’t increment a pointer unless you’re in unsafe mode? Is that what it’s saying?

Wait a second, safe C++ is C++ that’s not doing anything. So everything inside of here will have to be run in an unsafe block. So let me get this straight: you create a function that’s safe, and then every last part of it is unsafe from there on out? Well, actually, if you write C++ at all, it’s actually unsafe, so you have to wrap all of your C++ in an unsafe bracket. You start by calling the function safe and then you wrap everything in unsafe.

Yes, pointer operations are obviously unsafe. I don’t think it’s very unintuitive to realize why pointer operations aren’t safe in the sense that when you add one to a pointer, is it within or without the bounds? Should be pretty obvious.

The function is basically to restrict a bunch of smartass type of code in its body. One could then use the unsafe keyword to call the plain old C++ functions or to turn safety features inside the safe function off for a block of code. All old C++ code is considered unsafe. That’s also why this proposal brings a new standard library in the std2 namespace. If the standard was so good, why not make standard two? They made standard two; you standard two just dropped it. It actually is that good.

You have to understand something, which is any set of functionality that...

=> 00:13:52

C++ is evolving to prioritize safety, but the challenge lies in balancing new features with the complexity of its syntax and legacy code.

When you add one to a pointer, is it within or without the bounds? This should be a pretty obvious function to basically restrict a bunch of smartass type of code in its body. One could then use the unsafe keyword to call the plain old C++ functions or to turn safety features inside the safe function off for a block of code. All old C++ code is considered unsafe. That's also why this proposal brings a new standard library in the std2 namespace. If the standard was so good, why not make standard two? They made standard two, and they just dropped it. It actually is that good.

You have to understand something, which is that any set of functionality that lives on long enough will eventually have a number two version. It will happen no matter what; it just happens every single time. This, to my understanding, will replace the whole standard library with safe alternatives. The proposal defines memory safety using the following categories: lifetime safety, which basically addresses problems with using uninitialized variables or, for example, use-after-free bugs. To address this, safe C++ will feature the borrow checker, which, by the way, a number of other languages also try to implement.

Type safety, for example, addresses everything wrong with null. To prevent this, the safe context will prevent the use of uninitialized variables and bring a new form of a destructive move called relocation, which means that you won't be able to use a variable after you move it. I suppose this is closely connected to the borrow checker as well. Then, I wonder how bad the syntax will be for this because the thing about C++ is that it's really modern. C++ is not a bad language, but it's just a language where you type the word colon like 9,000 times.

I feel like the syntax is something I have a pretty hard definition about when I'm writing stuff. I don't like to exceed 80 characters; I know that's a weird rule, and a lot of people are different. I do exceed it every now and then, but I don't mind it. This is just a general rule of mine: when I start exceeding this a bunch, it means I need to refactor. That's kind of always been in my head. However, when I write C++, it just has to be that I have to go to 120 characters. There is just no option; you can't help it. The same goes for Rust; I do the exact same thing. I also have to go higher because Rust has the exact same problem, which is just that there are a whole bunch of colons.

I'm also curious how they're going to enforce this, considering that C++ has only really been written in unsafe versions or with unique pointers. Unique pointers obviously would fit nicely into this safe paradigm, as would shared pointers. They seem to fit nicely into this paradigm, but how much of C++ is just raw pointers everywhere? By the way, Dan actually is somebody who works in C++ all the time; that's his primary job at the ISS. He went to one of my courses that I did for Frontend Masters, and he couldn't follow along because he didn't have Node installed. The bro's real, okay? Not a kich eater.

The issue is that most people don't use anything past C++ 14, except folks who really need the newest features, like high-frequency trading firms. So, if this has any overhead to it, I don't see it getting much adoption. I could be wrong, but that's just my opinion.

As for the interaction with Rust and any of the C or C++ libraries, though you can do FFI, it all exists. There is a lot of glue and difficult code. Are you going to get the same thing when you interact with other C++? Are you going to have a whole bunch of just stuff you have to do? Does that mean every pointer that comes into a safe function needs to be converted into a unique pointer or something that is safe? I don't know; I have no idea. It seems like a very difficult problem, and it seems like you're going to get a lot of just code to make safe dread saf, among other things.

=> 00:17:49

Zig could be the breakthrough language that finally makes working with C and C++ safer and more intuitive.

That's an interesting take! I'm just curious, how do you manage working with Rust and any of the C or C++ libraries? Though you can do FFI (Foreign Function Interface), there exists a lot of glue and difficulty in the code. Are you going to encounter the same issues when you interact with other C++ libraries? Does that mean every pointer that comes into a safe function needs to be converted into a unique pointer or something that is safe? I have no idea; it seems like a very difficult problem. It appears that you will end up with a lot of code just to make things safe, especially regarding data races and problems with classic synchronization.

I work in graphics, where there's nothing but C++. I'm genuinely curious if Zig is actually going to make a dent in this. This is the reason why I wanted to learn Zig, which feels like the first language to actually make a step forward in the programming world that interacts with C and C++ in a real fashion. It seems to provide a version that is actually useful, without requiring a whole bunch of extra work when Zig 1.0 is released. I think that's Zig's biggest challenge.

As for Odin, I have not used it yet. That's why I asked if I should use Odin; I'm just curious about that. Zig has all the same primitives, such as mutexes, and this will be addressed in the same way as in Rust, using send and sync interfaces. To quote from the proposal, a type is send if it is safe to send it to another thread, and the type is sync if it is safe to share between threads.

Finally, regarding runtime safety, how are they implementing that? Obviously, there are traits in Rust that you have to use, and you have to apply certain attributes. Do you have to do that in Zig as well? That's interesting! You have to throw on some attributes, and I assume you can use concepts to narrow down what is allowed. Think of it as ensuring it is safe to share between threads, along with runtime safety measures like inserting panics before index out of range and other bound checking of various types.

In my opinion, a Reddit user said it best. Here is a list of overall improvements from his point of view: add a safe specifier in function signatures and an unsafe block. Safe code bans a big pile of operations, such as no pointer arithmetic, no pointer dereferencing, and no pointer comparisons. You can just say no pointer, which might be more useful for everyone. You can't just "raw dog" a pointer in here; it's not going to work, and you're not going to be happy.

Additionally, there is no use of union members, no inline assembly, and no use of uninitialized variables. They've added some new reference types that enable borrowing and borrow checking. Most types gain runtime bound checking, with the option to skip the bound checks where performance really matters. There's also a new operation, DRP, to call the destruction on a local object explicitly and set it back to the uninitialized state. Finally, there are thread safety guarantees based on Rust's send and sync type traits.

Personally, I wonder how anyone is going to program C++ without all these features. I'm also curious about the reference type because there has to be some way to relinquish a reference back out to unsafe C. My brain is struggling to understand how this works. How does it function? I can feel my brain chugging along on this point.

How do you return a pointer instead of a reference? Does this add a new L value, R value, or X value? Do we get a new one, like an S value for a safe value? Based on Rust's send and sync type traits, I like the idea overall, even though this is still not finished. I think that if the semantics and syntax remain familiar to old C++ developers, C++ might actually remain relevant not just in the next 200 years but also in the next 300.

That said, I avoided showing you syntax examples from the proposal since it's not 100% decided yet, and to be frank, I'm not sold on the syntax. I still don't believe it. Back to Rust, I know it looks worse than Rust; this actually looks significantly worse than Rust. This is Perl! This is a joke; we're getting joked on. This is just Perl; that's all it is. I refuse to believe that this is real.

What is this? My entire existence feels like a lie. I'm just trying to run this in my head. We must dereference the pointer, right? No, no, no, no! The order of operations must be that we increment the internal pointer, then dereference that internal pointer, and return it as a reference in an optional object. Did I get that correct? Am I now a C++ master?

This must not be XOR since it's a prefix operator, and I'm not even sure how you would parse that. How do you wait? There's also a postfix; it has to stand for a reference. How do you do an XOR operator then? How does it know that an XOR operator isn't a prefix operator? You're going to have to use parentheses, aren't you? Monads are easier than this!

I know there's TI; I assume it does some sort of pointer casting. If you look earlier under the attributes, you can query the interface values as T is send and TI is sync. I assume that's what it is. Now zero will become a null pointer, and XOR will do something crazy. To be frank, I'm still not sold on the syntax, but I believe this is going in the right direction. I wish these guys the best of luck with the proposal.

As I mentioned earlier, I'm also interested in how Carbon plans to address these things. For example, what do you think about the proposal? Is the change welcoming? Do you like it? Let me know in the comments below. As always, I hope you guys had a good time. Thanks for watching, and I hope to see you again. Bye-bye!

That was fantastic! What a great video! This accent and this voice, I swear, exists somewhere else, so I'm confused as to why I'm hearing it again. But I believe this is a new channel, "Cheesed Up." Hey everyone, go subscribe to Cheesed Up! That was a great video, by the way.