Skip to content
Rust vs. C++ with Steve Klabnik and Herb Sutter episode artwork

Oct 23, 2024 1 hr 1 min

Rust vs. C++ with Steve Klabnik and Herb Sutter

from Software Engineering Daily Hosted by softwareengineeringdaily.com

In software engineering, C++ is often used in areas where low-level system access and high-performance are critical, such as operating systems, game engines, and embedded systems. Its long-standing presence and compatibility with legacy code make it a go-to language for maintaining and extending older projects. Rust, while newer, is gaining traction in roles that demand safety and concurrency, particularly in systems programming. We wanted to explore these two languages side-by-side, so we invited Herb Sutter and Steve Klabnik to join host Kevin Ball on the show. Herb works at Microsoft and chairs the ISO C++ standards committee. Steve works at Oxide Computer Company, is an alumnus of the Rust Core Team, and is the primary author of The Rust Programming Language book. We hope you enjoy this deep dive into Rust and C++ on Software Engineering Daily. Kevin Ball or KBall, is the vice president of engineering at Mento and an independent coach for engineers and engineering leaders. He co-founded and served as CTO for two companies, founded the San Diego JavaScript meetup, and organizes the AI inaction discussion group through Latent Space. Please click here to see the transcript of this episode. Sponsorship inquiries: [email protected]

Episode details come from the podcast's RSS feed.

Embed this episode

Ready to play

Rust vs. C++ with Steve Klabnik and Herb Sutter

0:00 1:01:50
of matches

Transcript Auto-generated, may contain errors

In software engineering, C++ is often used in areas where low-level system access and high performance are critical, such as operating systems, game engines, and embedded systems. Its long-standing presence and compatibility with legacy code make it a go-to language for maintaining and extending older projects. Rust, while newer, is gaining traction in roles that demand safety and concurrency, particularly in systems programming. We wanted to explore these two languages side-by-side, so we invited Herb Sutter and Steve Klobnik to join host Kevin Ball on the show. Herb works at Microsoft and chairs the ISO C++ Standards Committee. Steve works at Oxide Computer Company, is an alumnus of the Rust Core team, and is the primary author of the Rust Programming Language Book.

We hope you enjoy this deep dive into Rust and C++ on Software Engineering Daily. Kevin Ball, or KBall, is the Vice President of Engineering at Mento and an independent coach for engineers and engineering leaders. He co-founded and served as CTO for two companies, founded the San Diego JavaScript Meetup, and organizes the AI in Action Discussion Group through Latent Space. Check out the show notes to follow KBall on Twitter or LinkedIn, or visit his website, kball.llc. Hey guys, welcome to the show. Thanks so much for having me. Yes, I'm excited to do this special episode. I'd love for each of you to introduce yourself. So let me throw it first over to Herb. Herb, let us know who you are, your background and what you're into. Oh, I'm a programming languages nerd. I've been doing programming, especially with an interest in systems development for quite a long time. And I've been paid professionally to do development in a bunch of languages. In recent years, that's been C++ a lot. And so I've been involved in the C++ standardization world, but I'm always interested in different and new languages and sometimes in old languages. Yeah. And Steve?

Yeah. Hi, I'm Steve. So I first came like known professionally because I was working on Ruby and Ruby on Rails, but I actually learned C when I was like 12 or 13 years old and then learned C++ back in 98. Stuff has changed a little bit since then, but more recently over the last 12 years or so, I've been using and working on the Rust programming language. I was in the core team for almost a decade. I was the person behind the Rust Twitter account for a long time. So if you ever tweeted a Rust, you probably talked to me. Lots of other stuff. So yeah.

Well, and that naturally brings us into kind of the premise for this show, which is that C++ and Rust are often compared against each other. They're both incredibly performant, close to the middle programming languages. They don't require runtime. They're really good for systems programming, all these different things that everybody knows. But I feel like a lot of times when we hear a comparison, it's like a partisan of one being like, oh, you should always use this, whatever. And no matter how hard they try, there's some amount of bias. So our goal today would be to kind of have counterbalancing biases, allow you to each represent the language that you've been focusing on this last little while and kind of share strengths, weaknesses, talk about the communities, those whole sort of domains. So maybe let's kind of start with going to each of you. What do you see as the strength of your language of choice here?

We can start this time with Steve. So for me, like the natural normal answer here would be something about memory safety. But I actually think that while that is really important, I don't think that really gives enough credit to like the Rust project overall. I think that one of the things that Rust does when it was first announced like internally to Mozilla, I believe it was like programming techniques from the past come to save the future. And Rust takes a lot of ideas that have not been popular in more recent programming languages, but they're well known in the programming languages space and sort of brings them into a more contemporary context and adds a whole lot of other things like cargo and other stuff like that. And so I think one of the greatest strengths of Rust is

coming along so late in the overall programming language history that it's been able to take advantage of all of the knowledge of the languages that have come before and then trying to build on top of that and use those tools in a new way. Awesome. And Herb, what would you say for C++? Well, first of all, I use multiple languages, but I do a lot of C++, and it's where a lot of my work has been in recent years. And I love that it's a language where I can always open the hood and take control.

So we often say a systems programming language. I'm not exactly sure if that's the best term because I know Go has been described as a systems language, but they mean something different by that, orchestrating data center systems. To me, one litmus test is, can I write a memory allocator in it? Because a lot of Java programmers learning Java at school, you ask them, okay, implement a memory allocator, and they don't understand what the question is. And sometimes you get the answer back, okay, a new widget. Okay, that's what we want to implement. And so that's just a litmus test, but also just for performance and control for zero overhead abstraction, not zero cost abstraction. Abstraction often costs things, but where you couldn't do better by hand in a lower level language, say like C, and be able to express what you need to express.

without mandatory overheads, like always on GC and things like that, which I'm so glad to see Rust and Swift avoiding as well, because I think there's a real need for languages that make good use of the hardware that we have efficiently. Yeah, I feel like that's one of the things that really sets these languages and you bring Swift in as well apart is they don't have a runtime. They don't have the sort of costs that you have to pay regardless of whether or not you want them. And going into this zero overhead abstractions, I'd actually, I feel like that's something I've heard in both communities, a fair amount of emphasis on. I'd be curious if there's any differences in your takes on that. So what I'll say is that

The Rust world tends to use the like word zero cost. And that's actually because of like a divergent historical fork of like the C++ folks have realized that that name is not actually very good. And so zero overhead abstractions is like a better name. And so I've been using it for like a long time now. And the Rust folks just like never really caught up. It means conceptually the exact same thing as far as my understanding goes in the two communities. It's just kind of like a weird thing where we use some of the older terminology. And like, I fully agree that you get into, as Harv said.

Everything has a cost. Like people are like, it's not a zero cost abstraction because it makes compile times go up. And it's like, okay, that's like not the cost that we're talking about when you talk about cost. And that's why like it's such an easy thing to start arguments about on the internet. But like the underlying idea of you don't pay for what you don't use is like the core general idea for sure. And I'll go ahead and throw some arrows at chinks and C++'s armor there because when you talk about zero cost or zero overhead abstraction, so the idea is I can express things a higher level way.

Usually that means I can declare my intent more directly, say what I want to do rather than the details of how to do it. So the code tends to be smaller, it tends to be more optimizable by the compiler because it knows what you're doing now. Oh, that's what you want to do, let me help you, that kind of thing. But when you look at where C++ is today, there are two places in the C++ language itself where every compiler has a way to turn the feature off, and that is exception handling and runtime typing. And if you look at why, it's because those two can be written better by hand, or you pay for them even if you don't use them. And so I think it's been instructive to see that largely we've done a really great job at that. But hey, the two things that everybody has a switch to turn off and that to some sense can have some bifurcation in the ecosystem between, you know, my library supports errors by error codes, I throw exceptions in this other library, comes down to, ah, it's those places where we violated the zero overhead principle or the zero cost principle. And I think that's instructive to learn from.

There's a similar situation in Rust where, so we call them panics, not exceptions, and sort of go down that idea. And so most of the time you're returning, not error codes, but like the moral equivalent of fancy error codes. But like you can say, I don't want panics or I want panics to abort is the name of the setting, like panic equals abort. And a lot of embedded projects in particular use that setting because adding the handlers for all those things takes up code size. And like sometimes it can cause optimizations not happen because of the fact that, you know, something may panic here. And if you turn them into abort, then like things are able to go better. So that's definitely like, it's a goal, right? Like you're trying to like keep it in a principle in mind when doing design. It doesn't mean you can always fully completely get there.

Yeah, panics and exception handling and the returning by error codes and other ways. It is a great place to start with any language. So for those listening, if you're picking up a new programming language and you want to sort of get a sense for what its sensibilities are, one good place to start is to see how does it handle error handling? Because that will tell you a lot about the philosophy of the language, including if there's not one answer. That also tells you something about the philosophy of the language. Yes, yes. What is it? Erlang that says there's no errors, they're just events that go to a particular handler? That tells you a lot right there.

I'd love to dig in a little bit more on this, since it seems to be common around the places where you have chosen to not do a zero cost abstraction to say, oh, this abstraction might be worth it, even if it has a cost, but we'll make it optional disabled. Like, is that coming from a coherent philosophy? Is that something that was sort of accidentally happened to? Are there new opportunities for that type of extension? Oh, there's lots of abstractions that do carry costs. But I think the philosophy, Steve, it sounded like you were saying the same thing. As long as I don't pay for it, if I don't use it, that still fits the zero cost abstraction model. The question is, what's the cost if I'm not using it? And on the flip side, if I am using it, could I implement it better by hand?

Yeah, I think sort of a small Rust history tangent here because I think it's kind of instructive. Rust before 1.0 was kind of like four different languages at four different times. At least I used to say it was four. I don't know if I still fully agree with myself back. When 1.0 was happening, I tried to categorize what are the periods that Rust went through. And part of that is because very early Rust looked actually sort of similar to Go and Erlang more than today's Rust. There was a garbage collector. There was a runtime. Things were in tasks, not threads. And that's sort of because the driving philosophy or idea behind Rust has always remained the same.

But we found out over time that those goals could be implemented in a way that was more efficient. And this is where I kind of get into the zero overhead thing. Like the idea of memory safety without any garbage collection was fairly novel in 2006 when Graydon first, I mean, you probably type monotone in it instead of get in it. But like when the repo was created, it was like, okay, if we're starting from a position of memory safety, then we need a garbage collector. And that's just like the way that it is. And over time, like the history of Rust up to 1.0 is kind of slowly dismantling all of these abstractions that had a significant amount of overhead once it was kind of found out.

one of the primary mechanisms this was accomplished is types because types are a compile time construct not a runtime construct at least in this family programming languages you can argue about that with other languages and stuff but like basically you know the more that you could express in the type system the less you needed to check at runtime which meant the less overhead that you would have at runtime and so as the type system kind of grew more powerful and it got applied in different ways we're able to strip all that stuff away and like rust had a runtime up until november of 2014 and like rust 1.0 was may of 2015 that was like less than a year before 1.0 that rust gained arguably one of its most defining traits and that was sort of because The runtime was like pseudo optional, but it was not actually optional. The idea was that you would have green threads or also like system threads or like one-to-one P threads, basically. And you could write your code agnostic of which one they were and you could sort of flip a switch. Like, do I want the green threaded runtime or do I want the regular threaded runtime? And it turns out that like implementing that interface added non-negotiable overhead even to the supposedly more efficient case or to the least efficient case to the point where they were both effectively the same performance-wise. And so it was kind of silly and was honestly a giant pain. And so there was sort of a big fork in the road of like, okay, do we force everything to be this way or that way? And we decided to go with these sort of...

like lower level, no runtime approach. And so there's definitely a strange alternate future where Rust made a different choice there. And honestly, I believe it would become a relatively irrelevant programming language. I think it was like one of the most monumental choices that was made before 1.0. And so that was not recent, but that's like a great example of that process kind of like throughout that time period in Rust history. I think it's interesting that you're talking about a static typing versus dynamic typing, because I think that's actually an example where you can sort of segue into zero over an abstraction. The language has static typing, right? For Rust, for C++, statically typed languages. That doesn't mean that we haven't grown, right? As we've learned and added to our standard libraries, added dynamic typing.

So some languages have done that in the language. So if you look at C Sharp, they've actually gone and done dynamic typing, like plumbed into the language at the level of you need a new compiler to use it and the compiler participates. But in C++, and correct me, Steve, my understanding is the same in Rust, we have some dynamic types as well. In addition to the static class and those kinds of things, we have optional variants in any, which are, so we have the some types where any can store an object of any type, but only one at a time. And that is dynamic typing. And you can ask what type is in it right now. And the answer might be different one second from now. It's the same object.

But so that's dynamic typing. But we've done it as a library because we think that we have a sufficiently capable library building language where we can do that and still get a useful feature. Same with variant, which is like any, except it's restricted to a statically known list of types. So it's sort of in between. You have a static list of types, but dynamically it's one at a time. And optional is just optional. I know you have that one. You probably have other examples too, Steve, in Rust's standard crates. Yeah. So the situation is a little, I guess.

It really depends on what you mean by dynamic typing, because like I would argue that like an enum or a variant, I guess I don't know as much about how variants is actually implemented, but like enums and rust are fully statically typed in the sense that you do know the list of possibilities and that is checked by the type checker. But it's also true that that introduces like a branch at runtime, which is like sort of what ends up happening in a dynamically typed language. So like, you know, which way are you sort of like thinking about it is really you're getting into like the semantics of things. we have the equivalent of any, but it comes with a really interesting restriction in that it only can use the static lifetime. And so it's actually much less useful in Rust than it is in some other languages that have a similar feature because you sort of can't use any of the lifetime related machinery to it. And this is, gets into some really deep weeds around the way that lifetimes are implemented and what they mean semantically into the language. So I don't necessarily want to segue to that right this second, but we'll get back to it later. But there definitely has like, or you could argue, so the way that we do the equivalent of virtual classes is like trade objects and there's some differences in how they're implemented. But the core idea of like, okay, I know I have an open set of this kind of thing that has these methods and therefore, you know, what is it at runtime is not really known and you're dispatching to a V table to know which.

method to call which is like the same thing as an dynamic library and sort of bring it back to other languages too you mentioned c sharp adding dynamic typing the whole ruby and python world have been adding static typing to their language you know in like the reverse or whatever so everyone's trying to like figure out what the sort of like proportions of each of these features make sense and how they fit together and language design is just as much of an art as it is a science and it's really about the taste and as you mentioned earlier find out what the philosophy of the language is that's like really really important and then different people sort of choose how much of this and how much of that they want to add based on you know whatever their take on the world is so yeah definitely And every language is designed to solve a different set of problems, right? So it's not like all languages are going to converge to be the same thing someday once we figure out what the perfect language is, because a scripting language is just not like an imperative systems language, for example. I could say more about that later, but I'm not going to use C++ in places where I should be using Python. That's just madness. But as we learned earlier, you were both saying that we're so far along in this industry. And it's true in terms of our careers, because the industry is older than, as I look at the number of gray hairs, we can see each other in the video. The industry is older than each of us. That's great. It is still a super young industry. I mean, the first compiler was what, 60, 70 years ago on the outside.

Thank you, Grace Hopper. But we are such a young industry. It is no wonder that we're still figuring things out. And I think it's great to see the newer languages not forgetting the lessons that we've already learned. It would be so easy to say, oh, look, I reinvented this new thing. Oh, you mean the same as that 40-year-old thing? But in that sense, I still think that all programming languages, including C++, including Rust, are young because our industry is still learning itself. And we're still somewhere between craftsmen and actual engineering, and we're navigating that transition. So maybe I'm just talking like an old timer now, but it seems like we're all pretty young and still getting the hang of this.

You're totally right. And I fully agree. I have a small interest in like urban policy and shenanigans like that and cities and stuff. My mom goes, hey, there's this book you might want to check out. It's called A Pattern Language. And it's about like how they would like manufacture houses. And I was like, mom, you're not going to believe it. But, you know, I've read this book, but for a completely different reason, like related to my computer stuff. But, you know, we think about when people ask me what I did and they're not software engineers, I would be like, well, you know, if writing programs was a construction job, I would be a hammer manufacturer, you know, but like steel that goes into hammers is thousands of years old.

And like, where, as you said, we're less than a hundred years old as an industry overall. And so we have a long, long way to go in terms of just like figuring basically anything out. So yeah, absolutely fully agree on that front. And as another small kind of note, I'm interested to see what programming languages are being started roughly now, because if you look at. the like big waves of languages that became popular. Like you have this stuff in the sort of seventies and eighties and that's like a little bit more fuzzy, but like 1994, 1995, I don't know what was in the water that year, but like four or five languages that are massive were all started like within a year of each other. And then you kind of have this sort of like rust go ish crowds that like happened around the 2009, 2010 kind of period. And so like it's 2024 already. We're like at the point where that period repeating itself, like there should be some people somewhere cooking up some new languages that are eventually going to be super huge. And there's some that have been started obviously in the last five or six years, but I think there's like somebody who is.

Starting a repository now that is making a programming language is going to be a huge hit in 20 years. I just have no idea what that is yet, but I'm excited to see how it happens. I'd love to dive in a little bit on something that Herb said. So you mentioned that the perfect general purpose language doesn't exist. These are designed for particular problem domains, and each language is making different trade-offs based on which domains they're sort of centered on. And those obviously evolve somewhat over time. And some of, I think, the trends that you're talking about, Steve, of adding static typing into Ruby and Python and TypeScript, JavaScript is like, oh, these things are moving beyond being scripting languages and used for large-scale programs where static analysis is really quite helpful. What would you describe as the sort of premier targets for Rust and C++? What things do those languages optimize for?

in their ongoing development, and what do they make easy? Yeah, I think those two languages actually have a very similar target. There is still a delta of where when would you use each one because of maturity of ecosystem, because of thread safety guarantees, which is pretty much unique to Rust, although I hear Swift is on your coattails there. And so there's still some differences, but in terms of the target of what problem, what kinds of programs do we want to support, I think there's a lot of overlap between those two, which is why we have fun conversations about which to use when and how can we learn from each other. Yeah, I definitely think this is true. I think...

One area in which I would have fully agreed with something you said earlier, but my thinking has changed a little bit lately, is that Rust has some more applicability, a little bit higher in the stack than I would have guessed. If you would have asked me 10 years ago, I would say, absolutely, I would never use Rust where I would use Ruby, but I am writing a web application in my day job right now, and it's going pretty great. Do I think that's necessarily appropriate for everyone? No, but an area that is an ongoing amount of focus for Rust specifically is networked services more generally, not necessarily backends to websites, but like... Cloudflare, for example, is using Rust code as their backend and they power 10% of internet traffic. You know, other companies that are writing services find that like a company you'd heard of, but I don't remember if this is a public story, but also it was like six years ago, had some stuff in Python and they rewrote it in Rust. And then they were able to drop thousands of servers off of their like cloud bill because the increased efficiency paid for itself or whatever. And so in terms of like, I'm not gonna say C++ is bad for those things, but it's not clear to me. And that doesn't mean that it's right or wrong that like networking services in the area where the committee is.

like specifically focusing on is an area of interest but it is a place where the rust team is putting a lot of time and effort into every time you hear people say async that's like effectively what they're saying although also it is useful embedded too and that's like an important part of the async story but they're definitely in the mindset of like network services more and that's because like in practice of the largest companies that are using rust we're only recently seeing people use it in sort of the like kernel space or the like embedded space but the first area in which it picked up significant traction amongst large tech companies was network stuff and so that's like an area that's like very actively having a lot of work put into it on the rest side One way I tend to answer the question at a high level is like, what kind of application, what kind of code would you consider C++ is when you need performance and control, you need control over space and over time. You need to know where your objects go, when they're going to be there, how they're arranged in memory, including adjacently and not in pointer chasing data structures on the heap because there's huge efficiencies there. And in time, determinism, knowing when things happen, knowing that the destructors will be run to have nested lifetimes for objects on the stack so that you can safely use each other and know that something declared before you outlives the thing that outlives you.

So control of space and time, being able to open the hood and take that control, I think is important. And my impression is that's pretty much also true at this very 50,000-foot level of answering where would I consider rust, right? Where I want that level of systems control. I don't want to have a garbage collector pause where I can't control it. I don't want overheads of taking a 2.5x total working set memory because of GC and having to have those tracing overheads. And I think that's a very similar value ideal. So I think one of the things that is true about C++ is you obviously have so much more history.

because the language has been around so much longer. I'd be curious to hear a little bit about the ways in which that is helpful for C++ developers and where that ends up getting in your way. Oh, that's going to be hard. Let me try real hard for a short answer. The short answer is because you have history, you have mistakes of history, and so you have to overcome them because assumptions change, for example, over time. Internet attacks wasn't a thing in the 1970s when C was first created and when Bjarne started making C++ in the 80s. We were all very collegial, and if we had a network, we were all trusting each other in universities. It was a happy time in some ways.

On the other hand, one of the challenges I see right now, so I work on the C++ team at Microsoft, which is also the Rust team. So we support Rust use in Microsoft and the C++ use at Microsoft. And we love both languages. And one challenge that we have with Rust is, gee whiz, teams that want to use it can't yet, not because it's not a great language, not because it doesn't have advantages, it does in certain things, just because of that history. Because there's been 30 plus years of tooling already developed and Rust is no longer a new language that's been 1.0 for a decade now. That's still pretty new when you have like 30 years of mature and working C++ tooling.

That's not a fundamental problem. That's something that Rust will get to, but it just will take time. And right now there are things where I just, I can't ship code out of the company unless it goes through this tool and it understands C++, but it doesn't understand Rust. So I can't ship that code. If I write it to Rust, I'm not allowed to ship it without a VP exception that, okay, we're going to take the risk. That's just one example. So we'll get there. We have our backlog of got to upgrade this tool, got to upgrade this tool, but it's a longer backlog than you'd think just because there's just been so much built around the C and C++ as the way of doing things for so many years successfully. So history is a strength and a weakness. I don't know if that helps. Yeah, no, totally. I'm curious.

Do you, and this is for each of you, where do you feel like, and Herb, it sounds like you have visibility in both communities, which is great, but where do you feel like each community could really learn from the other? So I lurk the C++ standardization process way more than you probably would assume that I do. It's not like I read and understand every single paper, but I do try to keep abreast of what's going on. I think that describing either community as one coherent thing has long not been the case for C++ and is increasingly not being the case for Rust too. So there's a little bit of challenge, I think, on that side. But if we talk about the language development process, I think that there's sort of a big thing going on in some parts of the Rust world where some people want to make larger changes to the language than some of us would prefer.

I don't know if that's sufficiently gentle or not, but like this history thing I think is really important. And I think that one of the strengths of the C++ committee is its total commitment to backwards compatibility within reason. Obviously, like some things occasionally get removed, like the garbage collector support that literally no one implemented. So if it's not in the standard anymore, that doesn't affect anyone or, you know, some minor backwards incompatible changes here and there, but that's just always going to happen no matter what. But like they have a very clear and very firm commitment towards that. And I'm not saying that the Rust team necessarily doesn't have a similar view like in the large.

But there's been some recent proposals about some things that kind of like add a degree of complexity that I am not necessarily personally comfortable with or is more controversial amongst some parts of the community due to that kind of like, does this actually pull its weight? And there sort of tends to be like a push and pull about is adding a new feature always a good thing or not? And so, you know, there's sort of like, I think a little bit of that going on. That's definitely something that like could be learned from, I think. It's interesting you mentioned that because it actually makes me think several C++ examples because this is language agnostic, right? We all deal evolution. Congratulations, you're successful. Now people want you to do more and there's different forces in different directions.

But the example that comes to mind is actually C Sharp. So I've been at Microsoft for over 20 years and C Sharp's our sister team. We talk to the C Sharp folks all the time, the language designers, the runtime implementers. And this reminds me quite a bit, and I know I'm not telling tales out of school here, this has been said before, but C Sharp added a feature that you and I would recognize for safety, important for safety, right? Nullability.

having nullability in the language, like being able to actually have first-class support for nullability is an obviously good thing, right? So of course we should run do it. Well, they were smarter than that and actually had a discussion, but then they did do it. And it's not clear that there's not regret about it because it turns out that at least the way that C-sharp did it, and this is an example of, I think what Steve was hinting out of hidden costs and hidden complexity, because there's complexity for users and the complexity in the language now, but also for the next 20 years, because you're going to be supporting this thing forever, right? And it's going to constrain evolution forever. It turns out that once you add nullability,

It's like adding a drop of ink in water and it spreads and it spreads. The whole language needs to know about it more than you first thought. It spreads throughout the type system. It spreads throughout the runtime, the IL. It touches everything. And it's not that there isn't benefit. There is to having nullability in the language, right? It's clearly the billion-dollar mistake and everything. There's clearly benefit. It's just the cost. How do those scales balance out? Because it turns out the cost is actually surprisingly high. And of course, maybe this could be different if it were done different ways. But the actual practice is C Sharp has it. It's great. But boy, did it cost more than they thought, knowing that it might not have been done or done in the same way.

if there was a time machine. And by the way, all of us get to the point where we have the discussions in our language design groups is if we had a time machine, we would do this. Since we don't, we're going to have to move forward somehow. Do you ever have those time machine discussions? Does that come up, Steve? So, I mean, just to be clear, I haven't been involved in the language for a couple of years at this point. So I don't know what the current thoughts are on that. I have a very jokey answer to this question, which is I think that the standard string type should have been named stir buff instead of string, but that ship has long sailed. And that's because of like inconsistencies with other parts of the standard library where other things are called path and path buff, but we have string and stir. And so stir and stir buff would have been like conceptually simpler. And that's like a breaking change that can never be made.

I used to have a good list of these things, but off the top of my head, I don't remember, but there's definitely ones where I was wrong. Like it was the inverse question where I fought against the change that I thought would be bad, but it turned out to actually be fantastic. And so I'm glad that I got overruled on that or not taken care of. The primary one there being like postfix await for the async await syntax. So like JavaScript, F sharp and C sharp, I believe were the first to actually have async await before JavaScript, but JavaScript kind of made it super popular for whatever reason. And so you do like await and then an expression and then a semicolon. That is like awkward for several reasons in Rust. And so the idea came up of like, hey, what if we did postfix await? So instead you do expression dot await.

And then the semicolon, this was in like 2017 was when this discussion was happening and it was incredibly contentious. And there was tons and tons of arguments. And like, basically my argument against this feature was like, we have a lot of folks coming from JavaScript. They know the syntax already. If we have a weird syntax here, like we're adding complexity for like sort of what I don't think is necessarily a very large benefit. And other folks were like saying, you know, actually there are benefits to the post syntax. It fits in like a lot nicer. If you don't wait multiple times in an expression, you're not nesting up parentheses and all this sort of stuff. But I was kind of like on this conservative position of like, okay, the trade-offs are unclear. Therefore we should make the conservative choice.

years later, now that I'm writing a lot of code with async and await, gosh, I'm glad they went with post-ticks await because it's like so, so much nicer in so many ways. And we solved part of that argument about learnability by the Rust compiler knows how to parse the JavaScript style syntax and then we'll give you a specialized error message that says, hey, I see you're trying to write await here, but we actually write it like this and we'll just literally show you the code you need to write instead. And so that turns what would become a learnability issue into a very, very minor speed bump. And so, you know, at the time was not really thinking about, oh, we can solve this problem through tooling instead of, you know, through like the language definition itself. So that's like, I think a famous example where I was wrong about what should be added. But I think one...

Area that's definitely been discussed a lot by people is around the name of mutable references. So Rust has two different kinds of references, like ampersand T and ampersand mute T for immutable and mutable references. And you'll hear sometimes people call immutable references shared references. And that's sort of because ultimately, like Rust take on mutability is that it's kind of the dual of exclusivity. So if you want to mutate something, it also needs to be exclusive. And if you don't need something to be exclusive, it's also immutable. And so you can kind of look at that property.

via either lens primarily. And so there's definitely a significant number of folks who argue that we should have renamed ampersand mute T to ampersand unique T to sort of like imply this sort of uniqueness of access because they see that as being the more fundamental part of the trade-off. My personal take on this is that's one thing I would love to see in a Rust++. I think that that idea at the time that Rust was like coming up, you needed to use the language that was more familiar with people, which is the mutable and mutable side. And then eventually you learn like, oh, this also has these uniqueness implications. But I think that now that Rust has kind of like at least made that.

a little more mainstream, but it's now possible for a future language to do that instead. And arguably, maybe that's a better way to do it. I don't totally know, but that's definitely a commonly cited example of a change that people sort of wished that we had done. There's also a ton of minor syntactic things. Obviously people like, I wish we'd use equals instead of colon instruct assignments or things like that that are like not super big picture things, but are more minor details. But for the things that may be more impactful that you wish you could change, let me just ask about additions, because we hear a lot in C++ about Rust has additions. We should too. There's been proposals about that. And I've always been interested in that. I'm working on what would...

could we do if we took a once in 30 years breaking change to C++ syntax, but kept full compatibility? And so that's something I'm exploring myself. But in terms of taking changes more often, it's like, can you really make large changes every three years? So I'm curious. So some of the things you just mentioned, like changing at mute to at unique, is that something you could see? If there was consensus for it that this is a useful change, could you see that kind of change being made in an addition? Could you see changing a default, like private to public or something else, like something fundamental like that that changes the meaning of code being done in an addition? Yeah. So it's really interesting.

The answer to both of those questions is technically yes, but I think that implies something about additions that's actually not true. You just happened upon two examples that are very, let's say, flashy. So one of the reasons why additions work really well in the Rust world is because we only have one compiler. And basically, sort of the core idea of additions, for folks who don't know about Rust additions, basically the idea is that you can pass a flag to the compiler that sort of lets you opt in to newer changes that would be breaking. So a classic example is in Rust 2015, the initial addition, async is not a keyword. So if you want to name a variable async, you can totally do that and it will absolutely work. But in Rust 2018,

it is a keyword. And so that code would break. So how do you get around that? Well, we basically like when you start a new project, it will add the latest edition to your configuration. So you get the latest amounts of stuff, but as time passes, when a new edition comes out, you're still on the old one until you change that configuration. So in practice, your code never breaks until you like opt into those new changes that like would be breaking. However, technically like in like a.

This is the technical explanation sense, not in a like paragraph subsection CED says. The way that this kind of addition mechanism works is like many compilers, the Rust compiler has like 15 IRs. It's actually more like five, but like whatever. But the point is, there's kind of like an IR level called the mid-level IR or MIR that you can sort of think of as like Haskell's core, like core syntax, core Haskell, where it's kind of a fully desugared. everything is down to kind of like the most basic components sort of aspect of the language. And while it's not a stable interface, it is at least like a conceptually roughly stable interface. And so kind of the idea is that additions can only change stuff in those desugaring layers before you get down to mirror. So something like changing mute to unique as a purely syntactic change would absolutely work because what ends up happening is when it compiles one package for 2015 and one package for 2024 and 2024 adds unique or whatever, it would desugar to the same internal.

It's not an AST at that point, but like whatever data structure of the IR. And that's where the interoperability happens. It's a little bit like C-sharp and VB. They're different syntaxes, but they both compile to .NET IL. It's, yeah. Yeah, exactly. And so that's like much easier with one compiler than multiple compilers because, you know, a Gimple is very different than LLVM IR, for example, just to name two, you know, kind of that sort of also limits what can be changed in addition fundamentally, because we can't like, you can't like add a GC in an addition because that would.

really change the way that things work on a more deep level. And also funny enough, because the standard library is the standard library, while you can make changes in libraries on editions in some ways, we can't with the standard library because you only get one copy of it included in the program and it needs a single edition. So a lot of people are thinking like, oh, could we remove a deprecated? function in an addition boundary and the answer to that is like not actually because you have one copy of the standard library and if you were using the previous version it still needs to know what that code is so like the closest we could get and it's not implemented yet but i think people talked about is like hiding those functions from a visibility sense in a new addition would technically work but like you don't actually get to delete the old code in the standard library even if you were like removing something and that's kind of an example of one of the things that additions can't change and so i think it's a really nice system overall because it gives you some flexibility like we didn't have the co-await co-async you know problem that y'all did because of additions that's kind of what originally drove it was like how can we add new keywords without breaking everything else and like what's the middle amount of stuff but it does mean that some core conceptual identity of Rust has to stay the same even throughout those changes because of the fact that we can't make truly deep changes with language through the addition mechanism. So that definitely helps, I think.

That's a great summary. Thank you for that. I'm sure the folks listening will find that really useful to know what it can and can't do. And just to elaborate on the thing that you just mentioned, for those who don't know, C++ also added await because that's what all the cool young languages are doing. This is going back to C++ 20 now. But because we didn't want to break existing code that might use await as a variable name or otherwise, and we were still at that point not really wanting, and to some extent now, not really wanting to have contextual keywords, but we wanted globally reserved keywords. After much, much bike shedding, that is debating what color to paint the bike shed, in this case, the name of the keyword, it was CO underscore await. So co await, because we have co await and co yield.

And there are codependent jokes and other things in there as well. But all because of deliberately uglifying it in the name of backward compatibility. And any language that's been around long enough and has been popular and cares about compatibility because you actually have customers, congratulations, they're a blessing and a curse because they expect you not to break them, is going to run into this. And I've seen the C-sharp folks run into this as well. So that's what the co-await joke was, because it's totally true. I think it might be the only major language that doesn't name it just await. So it's three more characters. Can you think of any others?

I can't off the top of my head, but I also think it was a very good reason to do that as well. I don't think it's a knock against y'all either. It's about being responsible to your customers and languages have customers, as you sort of said. I like that framing a lot too. One thing I'd love to dig in a little bit, something you alluded to, Steve. So Rust has a single compiler that is managed by the team that is defining the language, essentially. It's sort of co-located, whereas C++, you have a separation to some extent between compiler implementers and language design or the sort of standards committee.

So what do you all see as the pros and cons to those different approaches? And I guess since Rust is more immediate and you're involved in it, do you know why Rust chose to go that route? Well, by default, you always start there, right? Yeah. Yeah. So on some level, we went that route because in practice, as Herb just said, by default, that's what you do. The reason that the C++ committee is in the place they are is because also the C committee was in the place they are, which is also why the ANSI folks before the ISO folks, and was that C got very popular and people wrote multiple compilers and they all had slight variance of what was going on. And whenever you have that happen, you need some sort of...

way to you know unify various implementations and so a lot of people imagine that like specifications get written and then people follow the specifications but in terms of successful specifications that's almost never what actually happens it is a bunch of people doing their own things and then they get together and like hey we actually need interoperability between the groups of us and so now we have to figure out how to do that and so that's kind of like historically at least my view obviously or the other opinions that's or understandings given that's what you do but like that's how i see that happening and so in practice like The reason this happens is just because there's no reason to divide work when you're still a nascent language nobody uses. So you have to get to the point where people care enough about using you in the first place. And so that just naturally is kind of the case. Now, there are some examples of alternative Rust compilers, but none of them are at the level of maturity where that's like a technically true sentence, but it's not actually what you mean when you say those words. So for example, MRustc is a fantastic project that...

can compile correct Rust code, but leaves out things like the borrow checker because they're not actually needed. They're needed to check correctness. They're not needed for correct code gen. And so MRusty is able to compile the Rust compiler and produce a binary equivalent output to what the regular Rust compiler would do when compiling itself. And so in that sense, it is an alternative Rust compiler and it's useful for bootstrapping, but it's not something you would be using for a day-to-day kind of development process. And someone is currently working on adding Rust to GCC. And so they have that going on and they're working on that, but it's in very, very early days and is not really usable to compile any sort of real programs just yet because it takes a long time.

As those projects mature, I mean, I'm not entirely sure if MRSC's maintainer plans on trying to be a full compiler eventually, but the GCC person or people definitely are. And so at that point, a sort of more standardization approach, I think, makes more sense. However, like, I think there's... there's a significant desire to never make it an ISO standard. Let's put it that way. And like not following those steps. And it's because the ISO process has a lot of requirements that don't feel like they pull their weight for the Rust crowd. And so, you know, there's kind of a lot of like that kind of angle of, okay, once we would do a standardization, is it under some sort of other body? Or is it just like, we'll have the Rust standards group that's not part of some other standardization process and would write their own. There is an ongoing desire to produce a Rust specification by the upstream Rust project. And they have a working group that's sort of working on that. And that kind of happened because the Ferocene project.

sort of forked the Rust compiler in order to get safety certifications because an automotive and several other safety critical industries want to be using Rust code. And so they sort of took the Rust compiler and then did the work to make it happen. But they also upstreamed the vast, vast majority of it. And so in reality, it's not really an alternate compiler so much as it is a very, very small fork. But in order to get those certifications, they had to produce a specification for a ferrocene. So we kind of have like some normative documentation written by the Rust team themselves. Some documentation that's not normative for the language, but it's normative for a compiler. And the compiler happens to be basically identical to the real compiler. So like, you know, and so like there's these forces that are coming into play now in Rust lifetime that are.

you know, maybe going to make it a little closer to the way that the, at least conceptually that there are alternate implementations, but we're just young enough that that is only really starting to come to the fore in the last two or three years. Yeah. And C++ went through the same evolution. Everything begins with one compiler, right? That's like, if you start with a language, somebody's got to be first. And Bjarne wrote Cfront, the compiler that transpiled. It was a full compiler, AST and everything. Just instead of emitting a bytecode for .NET or in emitting binary executables, it emitted C code. And then you could bootstrap itself. And that was a great breath play because it means it would work sometimes with a little cording wherever there was a C compiler. So big advantages that way.

But eventually people said, well, you know, what if I had a native compiler? And I don't remember if your dad, Mike Ball, he was around in those early days. He was one of the first ones, yeah. I don't remember if he was at the dinner I'm about to describe. It's funny that here our interviewer is like the son of the person who might be one of the people who might have been at this dinner table. I remember it was in the very late 80s or maybe just around 1990 or early 90s, where Bjarne says about he was at a dinner with himself, with Walter Bright, who was building the Zortec C++ compiler. And there were one or two other people. And because it was just starting to be where people were building compilers, C++ compilers.

native compilers that didn't transpile to C, but were native compilers for specific platforms. Walter was doing it for DOS and then soon after that for Windows, which was really important for C++ success. Then there was Sun and so forth. But the anecdote was they're sitting around this dinner table because now there were some other compiler vendors starting to appear. And they knew at the time, and one of them, I forget who actually said, this is probably the last time that we will be able to seat all the C++ compiler implementers in the world at one dinner table.

And they were right. So this is a natural evolution. And I do think, just again, speaking as someone who's on the C++ and Rust team, I do most of my work in C++, but I see the Rust stuff. The monoculture is seen as a risk. I mean, it is an issue that we want to at least mitigate, if not solve somehow. Nobody's going to just go build another compiler just to have a second one so they can point at it. But even if there is just one, how do we mitigate that risk? I mean, look at xzutils, xzutils. You don't want one point of failure, one place for a supply chain attack. And there's just too much badness going around right now.

That's not to be fear-mongering. It's doing fine as it is, but you need to take more care. You need to care. Every time there's an update, you need to take care of what you're ingesting. And that applies to pretty much any open source project, in fairness, too, including Clang and, say, GCC. But having more than one compiler, having a spec, and where the compilers implement the spec, not the other way around, but although sometimes you document what actually happens, but there's going to be divergence in the spec should be to make them agree. The third piece that you're going to need to build, and this is part of building out the ecosystem of tools over time, over decades, is you're going to need those conformance test suites.

And there's going to be vendors popped up because that's going to be an industry where somebody's going to get paid to write these conformance test suites so you can actually validate if there is some sort of standard, whether it's ISO or something, a separate standard development organization for the Rust language. And I now have a Rust compiler A, B, or C. How do I know it conforms? I'm going to run, in C++'s case, Perennial or Plum Hall or one of those test suites. And in Rust's case, that's going to need to be built out so that we can then mechanically validate.

conformance as well. And this is just part of growing and becoming popular is you need to build out all this boring stuff, which is much less sexy than language design. It's much more fun to implement a new language feature. And then there's all this scaffolding and architecture and, oh, can't we just wish that wasn't necessary? But it's part of being well on your way to a million customers.

Yeah, luckily, Rusty has a significant number of tests already. So I'm not saying that functions as a conformance test suite, but at least would give the start of doing so because it's not set up to run, you know, a different compiler and report. You'd be like changing the scaffolding, but there's at least thousands of tests written that are, you know, a small amount of, you know, conformance checking. In particular, there's a file I really love called weirdxpers.rs that has lived since the very early days when the parser was being worked on. And it sort of includes...

Stuff that's like you'd never write the code, but it ends up being very strange to parse. So like there's union as a contextual keyword. So there's like a union parametrized over a lifetime union with a member union that's got like a type union and like, you know, just like silly code like that. There's also some very fun vestigial things since it's before Rust 1.0. For example, there's a function called evil Lincoln that just says like let unit equals println hello world or whatever. Like why is it called evil Lincoln? And it's like, well, in the olden days of 2009, the print statement was called log.

And Lincoln Logs are a popular toy for children in the US, or at least they used to be. I don't know if they're still a popular toy now, but that's what it was testing. Yeah, okay, cool. But the name got changed. And so the semantics of that joke got lost to history. And so there's lots of stuff in there. But it's also, I think, another great example, a reason I would want to see a new Rust compiler is actually based off of the work that Chandler is doing with his new language, which I'm totally drawing a blank on right now for some reason, Carbon. He's given a number of talks about the architecture of Carbon and how they're doing a data-driven approach. And that is an architecture that tends to work really well in Rust. And a very interesting thing about the Rust compiler is that...

it was written in a pre-1.0 language and then slowly translated over time. You know what I mean? So I'm not going to say that it's bad. I don't want to upset anyone. There's a lot of very talented people who work on the compiler and I'm not trying to denigrate their work, but they also are dealing with a million line legacy code base that is written before the language was even stable. And so there may be architecture decisions that they would want to revisit in a clean room implementation that are just simply not feasible in the current one. But I don't work on the compiler, so I can't speak to that being truthful. But I think that I found interesting learning about Carbon's approach to all these problems is the technology that they're doing to develop the compiler itself. And I think that's something you can often only do in a green room sense. So I think that's kind of a very interesting idea.

There's lots of reasons to want to, as time goes on, to decide, hey, I'm going to build another compiler. Well, you already have one that works. Why would you want another one? In C++'s case, early on, it was to have a native compiler rather than a cross compiler. Well, in the case of Clang, when Clang was developed and GCC worked pretty well, it was for mostly legal reasons as well as extensibility reasons. There can be lots of reasons to do it. Plus, we all hope we're smarter now than we were 15 years ago, even if we weren't also shackled by design decisions from 15 years ago. So there's always that impetus. C Sharp, for example, managed to totally replace their compiler, and a lot of people didn't even notice. So the Roslyn compiler...

is a drop-in replacement for the previous C Sharp compiler. And it has lots of other stuff and extensibility and toolability. So that was a reason to do that, that the previous C Sharp compiler didn't have. So why would you build a new one? Well, in that case, toolability was a big reason. But they managed to do it only because from day zero, from before a pencil was put to paper, that full backward compatibility was non-negotiable. And they developed the test assets and the quality gates. And they wouldn't let check-ins in that broke things from the beginning. And that added time and cost to building a new compiler. But it also meant they had a smooth rollout. But it couldn't have happened if they decided halfway through that, oh yeah, we want compatibility.

It's like, no, that's a decision you make on day zero, and then you live with it. But yeah, a lot of languages do this. I have no idea what Erlang does or what other languages do, how many compilers they've had or have. Yeah, C Sharp totally replaced their compiler in the last decade. C++ has many and has new ones springing up. One of my favorite multiple compiler stories, I'm curious, do either of you know about the infamous RMS missed an email story? No, but it sounds intriguing.

You could say the only reason we have LLVM today or the reason we only have GCC today, which GCC also had a situation where there was a fork that was semi-hostile and it got replaced at one point in time. But in the very, very olden days, back in the beginning, Chris Lattner sent an email to RMS saying, I would like to donate LLVM to the GNU project. What do you think about that? And RMS just missed it. So we never replied. And so Chris went, okay. And then they just kept on building it. But like, there is another fun alternate universe where RMS answered that email, accepted it. And LLVM either replaced GCC outright or LLVM became GPL licensed instead of, you know, its current license and like all that kind of stuff. And so it's very funny. You know, we're all really busy. It definitely does not give me any anxiety about my email inbox when I hear stories like that. But, you know, it's just sometimes history is just very interesting. So, you know, even then, like you get.

merges of compilers or, you know, forks where things come back in. It's not a compiler, but, you know, IOJS was a fork of Node.js over governance and then became the upstream after some situations. Sometimes, you know, you don't even necessarily have a permanent state of multiple implementations. You maybe have a temporary one that then gets reconciled in some fashion later. There's lots of different ways this stuff can play out. The diversity and the opportunity to have that diversity can be really helpful. I hope we see more of that picking up in the web browser space again. Yeah, it'd be nice. Speaking of monocultures, yes. Yes, exactly. We're getting close on time. So I would love to kind of wrap with one last question to you all, which is if someone is brand new to the systems programming world, they don't know either C++ or Rust.

What would you recommend them in terms of starting learnability? Where would you point them? I mean, I have extreme bias given that I literally wrote a book about how to learn Rust, but I would advocate for Rust simply because like, I just think that there's a lot of things about, because we did not have the history, there is an argument to be made that Rust is like, I don't want to say simpler. I hate simple in programming language, parsimonious. There are some things that are like have less.

There's no rule of three and rule of five because we don't have like constructors and like stuff like that, that there's just a little less to learn. I think that learning both languages is valuable. And I think that knowing one really helps you with the other. But, you know, as someone who has personally invested a significant amount of time and at this point in my life into making Rust a learnable language, sort of this way, I would hope that if they decided to choose Rust, that it would be successful and valuable to them. I think that what this answer is also different for every individual person. So I wouldn't ever say that like there is one universal answer because different people learn differently. I know tons of people that don't learn by reading books. And so those people would not be served no matter how much blood, sweat and tears I poured into the book over the years, you know, so like.

Obviously, I'm going to say Rust, but I do acknowledge that's not necessarily universally the correct answer. Yeah, I think Rust is a great first language to learn. I would much rather schools would actually teach Rust instead of Java, because at least it's a systems language. There are too many people who get degrees and don't know not just how to write an allocator, but what that means, right? And not everybody's going to need that level of understanding, but it would be nice to have more of that understanding in our CS graduates. And many are doing fine. It's just something that we can continue to improve.

If I had to recommend one, I would say pick one, like flip a coin and pick one and learn it and learn because so much is in common. One of the things that I've been finding, I run the C++ committee and I also run the largest C++ conference, the annual one CPPCon. One of the things that I've been noticing in the last, I want to say three, four years, especially, this is just happening organically, is every single meeting, every single conference, we're having local high school classes asking to attend. Like, yeah, let me go invite you as guests. I said the notification off to ISO. I just invited this class and they're like, what are their names? I invited the class, get over it. And they're like, oh, this is new to us.

teenagers coming to our conferences and in some cases giving talks is nice to see. And I think that speaks to that. We still have a ways to go to simplify the language, but it is much easier to use C++. Now, even though it's a bigger language, you don't need to know all of it to use it. You can actually get started much more easily than if it were, say, C++ 98. I would like to make that even 10x better, and I'm working on that as my own personal project. But to me, I would love to see more C++ or Rust be done in our universities, being taught in our universities, because systems programming needs to be taught more, I think. And C is fine too, but why not a higher level language that knows what types are, user-defined types are beyond struct.

There's benefits to that, to efficient abstraction. Yeah, there's a slight variation of your question, which is like, should I learn language X? And the answer to that is yes. for any X as far as I'm concerned. New languages help you change your perspective. They help you write better code in the languages that you decide to use more often. You develop taste. Taste is important. And the only way you can learn taste is by trying a variety of different things and figuring out what works for you. And I guess it brings back to a very early thing. There will never be one universal general purpose programming language. There's always an opportunity to pick one or the other. And you can't make that decision unless you try both. So you really got to just keep trying new languages. At the risk of going over, I just got to say this is reminding me so much of my undergrad days because sometimes you get the question, what was the most impactful course you had when you were an undergrad or something like that?

The two that come to mind, number two, is of course algorithms and data structures. That's bread and butter, and that's eye-opening when you're showing the implementations of B-trees and things like that. You're forced to think about algorithmic complexity, what big O means, and so forth. But to me, that's number two. The most useful one I found, which I just happened to get into, was comparative programming languages, where every week you learned and wrote an assignment in a different programming language. And to people who think that C and C++ and Java and Rust are different programming languages, they're not wrong. But who think that they are so different.

they should go take a course like that where you do Assembler in week one, Prologue in week two, and something functional in week three like Lisp. And you will never think that Rust and C++ are that different ever again, right? Because Prologue, everything is a rule. Assembler, everything is an opcode, it's an instruction. It helps you get out of the rut of, oh, programming is just this one thing and it's writing my Java application to finish my coursework. I found it to be really, really helpful.

That course was also incredibly impactful on me. I will tell you that story some other time. I love it. So this has been great. I feel like we could keep talking for hours, but we're going to wrap it with that. Thank you both so much. Thank you, Steve. Thank you, Herb. And with that, we will call this an episode. Thanks for having us. Thanks so much.

Frequently asked questions

How long is this episode of Software Engineering Daily?

This episode is 1 hour and 1 minute long.

When was this Software Engineering Daily episode published?

This episode was published on October 23, 2024.

Is there a transcript available for this episode?

Yes, a full transcript is available for this episode. You can read the complete transcript on the episode page.

Can I download this Software Engineering Daily episode?

Yes. Use the download control on the episode player to save the publisher-provided media file.