Hacker News new | past | comments | ask | show | jobs | submit login
Rust and C++ Interoperability in Chrome (chromium.org)
242 points by ytausky on Aug 19, 2020 | hide | past | favorite | 138 comments



> No need for the “unsafe” keyword unless something is known to be less safe than normal C++.

> For a Rustacean, this is controversial - all C++ is unsafe!

Isn't this just a fundamental misunderstanding of what unsafe really means, and as such a nonsense goal that doesn't gain anything?

Unsafe is a Rust language definition, and defines whether the rust compiler can vouch for the safety of some code. As such, calling to a library in some other language will always be unsafe, by definition, because the Rust language cannot vouch for it.

If it makes you feel better, replace "unsafe" in your head (or with a macro) with "rustc_cannot_vouch_for" and carry on with your day. It means the same thing, and isn't really telling you that your C++ code sucks even if it looks like it at first glance, so who cares?

If you really want to reduce the unsafe keyword from being seen in most regular code, you create and interface that maps the C++ function and exposes it as a rust function, and you've hidden away your unsafe usage to one spot per function. If you have enough confidence in your C++ code, there's no downside to this (and if you don't, then maybe you shouldn't be complaining that unsafe is unneeded).


The usual understanding is that an unsafe function is any function that can, if passed the wrong arguments, cause Undefined Behavior. A lot of Rust’s std and library ecosystem provides safe wrappers around underlying unsafe C APIs, by cleverly using borrow semantics and performing additional runtime checks. The bar for safety is lower than you might expect, for example, crashing with a “panic” in case of an error is completely fine, that’s not considered unsafe.

Presumably, the C++ wrappers suggested here would not even be safe in that sense. They would use a lot of pointers, and cause UB if passed the wrong one. The article argues that using unsafe correctly is impractical for Chromium because basically every Rust function would have to be marked unsafe to call into these raw C++ APIs.

Whether that’s the right call, I don’t know. I see the practical argument, but I also think that a lot of common Rust idioms, especially around memory management, depend on these safety guarantees. For example, field destruction order in Rust is weird and you often don’t control it as carefully as you would in C++. That’s usually fine in normal Rust code, but might not be if lots of marked-as-safe-but-actually-unsafe code is flying around.


> For example, field destruction order in Rust is weird and you often don’t control it as carefully as you would in C++.

This is well-defined, as being dropped in the order that they are declared.


I didn't want to suggest it's undefined, I said weird. In C++, it's reverse declaration order, and that's important because destruction exactly mirrors construction. Arguably, Rust is completely wrong here, but my experience is that this problem doesn't come up in idiomatic Rust all that often because fields can't depend on each other anyway. And I'm wildly speculating of course, but something like that might be more of an issue if a lot of unsafe C++ objects are instantiated from within Rust.


I see, I misunderstood how you said that, but I get it now. :) Sorry about that!

Yeah, the fact that it's the opposite in C++ was a huge point of contention before we defined it; changing what was there risked breaking a lot of code, and there are good reasons for either order. But yeah, I had to go and look at the RFC for exactly the reasons you state; it just isn't normal to care about this at all, so it's easy to not remember.


Just because fields can't depend on each other in code doesn't mean that the side effects of those fields don't matter: Rust is only guaranteeing memory safety, not semantic safety... like, if you are tracking file handles or networked objects or bicycle messengers, it can easily (and even often) be very important that objects deconstruct in the reverse stacking order of their construction. I am so glad I read your comment here as I could easily see myself one day starting to use Rust and going insane from this decision :(. (The more I think about this the more concerned I honestly am as I am not even sure how to implement many paradigms correctly without this basic deconstruction stacking order property.)


> The more I think about this the more concerned I honestly am as I am not even sure how to implement many paradigms correctly without this basic deconstruction stacking order property

Don't you just define a destructor[1] and make it explicit? Or maybe I'm missing something subtle here?

1: https://doc.rust-lang.org/reference/destructors.html


> The bar for safety is lower than you might expect, for example, crashing with a “panic” in case of an error is completely fine, that’s not considered unsafe.

Yes, because "safety" in English is not analogous to "unsafe" in Rust in any real manner. They are linked only in the loosest conceptual way. Rust's "safety" and "unsafe" are well defined and exact, and can be objectively determined. In the English language it is by its nature subjective.

> The usual understanding is that an unsafe function is any function that can, if passed the wrong arguments, cause Undefined Behavior.

That is, specifically AFAIK, not what it means in Rust. It's well defined there, so there's no need to resort to "the usual understanding". This causes a lot of confusion, but it doesn't need to. If someone is talking about the unsafe keyword in Rust, they're talking about Rust's definition of it and what it entails, or they're using incorrect terminology.

> Presumably, the C++ wrappers suggested here would not even be safe in that sense. They would use a lot of pointers, and cause UB if passed the wrong one.

Then there's not a lot of point in using Rust if they refuse to change that usage. It's not about using Rust to check off a box that you use some buzzword technology, it's about leveraging what it can provide that other languages can't, or can't do as well.

If someone finally decides to put a firewall in front of a server but decides its' too hard to actually limit traffic in any way so allows 0.0.0.0/0 any port, you don't congratulate them on their firewall, you tell them they just wasted everyone's time and money.

> The article argues that using unsafe correctly is impractical for Chromium because basically every Rust function would have to be marked unsafe to call into these raw C++ APIs.

To my knowledge, the way Firefox did it was to pick a system or library, provide a Rust replacement that C/C++ could call, and allow it to be moved into place. That's sane. Using rust and C++ functions on the same memory and expecting Rust to deal with C++'s pointer shenanigans may not be, in the same way I wouldn't expect mixing small amounts of Java in the JVM into a big C++ would just work unless you had hard rules about who controlled what memory/objects and made sure you never broke them.

I can't help but feel they're approach is likely to cause them more problems than needed because they're not willing to commit the sane minimal amount to make a lot of these problems not matter (replace a small self contained system). If they don't actually have any small self contained system, and they really are just passing stuff around and using some complex pointer control, and aren't willing to change that, maybe Rust isn't a good candidate to extend their project languages with.


> That is, specifically AFAIK, not what it means in Rust. It's well defined there, so there's no need to resort to "the usual understanding". This causes a lot of confusion, but it doesn't need to. If someone is talking about the unsafe keyword in Rust, they're talking about Rust's definition of it and what it entails, or they're using incorrect terminology.

I'd say that Rust is using incorrect terminology, since it is at loggerheads with "the usual understanding" (which should be the default, not something "resorted to"). Rust could have chosen a different word than safe/un-safe, but they didn't.


Okay, but don't forget to also tell that to C++ about their use of string...

Languages get to define their own terms. It's not rare for two languages to use terminology in similar, but different in fundamental ways, concepts. An extremely common example would be "threads".

In the case of "unsafe", we're not even really staying within the realm of software engineering to find a terminology mismatch in this case, we're going back to the English language itself (in which case almost all the keywords in any language might have a similar problem).


> ... known to be less safe than normal C++.

Oh... no, no, that's... very unsafe lol.

Jokes aside, making that their #1 goal was very strange. Agreed it appears to be a fundamental misunderstanding of what "unsafe" means. It doesn't mean that it's literally not safe to call that function, just that it's unchecked. "unchecked" might be a better annotation come to think of it.


> "unchecked" might be a better annotation come to think of it.

Yeah, it's come up before in discussions here. Depending on the context you're coming from/working in, unchecked either makes more sense, or less sense than unsafe. When working within Rust, unsafe makes sense, it maps to how people think about what they are doing, because rustc is checking everything. When working between Rust and other languages/libraries, it's a bit less accurate, and "unchecked" makes more sense.


Sounds like an organizational way of seeming open to something, but making sure it never flies.

“We are happy to empower our younger colleagues. We only ask they have 25 years of experience.”


I don't think they're misunderstanding the meaning. I think there'a just so much "boilerplate unsafe" for such cases that it would distract from other uses which they could meaningfully audit.


> If you really want to reduce the unsafe keyword from being seen in most regular code, you create and interface that maps the C++ function and exposes it as a rust function, and you've hidden away your unsafe usage to one spot per function.

Isn't that what they did? Through the use of https://github.com/dtolnay/cxx

Agreed the article expressed the concern in a rather clumsy manner, but they seem to have a point for their particular use case, and they addressed it appropriately.


Yeah, in the end, they're taking a somewhat sane approach, it's just rather odd that they identify this as item number 1 that needs addressing.

I'm not sure whether it's actually better to abstract all the unsafe away, or force it to shown where it's used. In one respect, they may be right, it might lesson the impact of unsafe and it gets used more freely. On the other, it also means that it's not necessarily immediately obvious when you're crossing boundaries, since the obvious bits and intentionally hidden away.

Though I'll freely admit my initial responses were a bit hyperbolic and... testy. :)


I disagree, you can automatically expose any unsafe function with a wrapper _safe function that does nothing more than call the former (possibly also wrapping fundamentally unsafe types in arguments and return values) which makes it appear safe but that doesn’t make it sane.

There would need to be domain understanding baked into each wrapper and some contract about what is happening on the other side of the ffi wall for it to qualify as both safe and sane, and I’m not sure their approach meets that high burden.


Yeah... The unsafe keyword is not unique to Rust either and exist in C# with similar semantics (although slightly different due to gc vs borrow checker memory management). One way to think of it is unsafe blocks effectively let you write C, with pointers and other memory unsafe operations inside of an otherwise safe language. This can be used for performance critical code (in a similar, but higher level, way as inline asm) but is primarily used to interface with unsafe C APIs (OS APIs, graphics APIs, etc) and libraries. Entirely agree that this is a misunderstanding.


The Rust definition of 'unsafe' is very far from the everyday meaning.

Yeah you can mentally substitute 'rustc_cannot_vouch_for' but why can't Rust use a word closer in meaning to that in the first place?

We tend to misuse everyday meanings I think. I reminded of

1. the msdos warning that 'this program has used an illegal instruction and will be shut down'

2. That DNS names with underscore are 'illegal'. This one caused an amusing kerfuffle at one company I worked at.

3. That certain crypto algos are 'broken'


> The Rust definition of 'unsafe' is very far from the everyday meaning.

"safe" is entirely relative in it's everyday meaning. To one person skydiving is safe, to another it is extremely unsafe. When you're a child your parent will tell you what is safe or not, but what your parent considers safe is based on their personal definition of safety.

The rust language will tell you if what you've done is safe based on its very well defined criteria for what that is, and won't let you proceed with something that doesn't match that criteria unless you vouch for it yourself. unsafe makes just as much sense here as any other word, it only seems a wrong fit when you look at it from a context it's not meant to be used in.


I'd suggest that 'unsafe' is quite far away from the opposite meaning of 'safe' in everyday language.


I'm reminded of "my phone has 64g of memory". It makes sense to a lay person, that memory would mean storage.

This is inevitable. We either invent new words, or we use existing words from the natural language. The latter will always lead to confusion at some point, because the domain-specific concept doesn't exist in the rest of the world. To represent such a concept, a word would need to deviate from its everyday usage.

I think Rust's "unsafe" is an okay choice; the "vouching" is a feature about memory safety after all.


On a very similar note, a line in Mythbusters' opening that always amused me, "We're professionals, and that keeps us safe." No, it doesn't, it only means that someone is willing to pay you to do it.

I think the use of "unsafe" is exactly what is a common case. That is declaring, "I know what I am doing," and that for most people do do this may be a bad idea.


> This seems to present some C++/Rust interoperability challenges which nobody else has faced.

Is this different from the Firefox integration with Rust in some meaningful way?

It looks like the cxx library is going to be critical for this. I’m curious how helpful others have found cxx for interop with C++?

> For a Rustacean, this is controversial - all C++ is unsafe! But “unsafe” should be a really bad code smell. If “unsafe” is needed for all C++ calls, there will be thousands of them, and the “unsafe” keyword will lose its meaning.

I don’t think the attitude about unsafe usage differs from the general Rust community. If there’s a lot of it, then it definitely is a code smell.


I use Rust. Unsafe is okay as long as there is a comment detailing why it is okay. Just like .unwrap() is okay for stuff that never fails.

What is indeed bad is code that sprinkles unsafe all over the place without explanation and if you check a random segment fails to handle common error cases that pure Rust would force you to handle.


> Just like .unwrap() is okay for stuff that never fails.

Never fails, and will also never fail in the future as other code changes. There are certainly situations where you can be sufficiently confident of that, but it's something to think about.


I assume the cxx library still generates unsafe code, but that can be excluded from analysis, i.e. no human-written unsafe is needed.


Yes. It must use unsafe internally and/or in generated macro code.

This is good documentation on the safety of the library: https://github.com/dtolnay/cxx#safety


Seems like the difference is the level of friction the project is willing to take on in order to support Rust.


Can't you just do a:

      #define CPP_CALL unsafe
in the Rust equivalent to annotate "unsafe" cpp calls from Rust?

"No boilerplate or redeclarations. No C++ annotations. Ideally, no allowlist."

This seems like an unpractical approach. How do you even call C++ code from Rust without extern "C" linkage.


Really, your text macro substitution is missing the broader point.

Hiding the word "unsafe" doesn't make it any less unsafe.

The Rust compiler can't guarantee that the C++ code being called doesn't have use-after-free bugs or buffer overflows.

The Rust compiler can't guarantee that pointers being returned from C++ code aren't just wild pointers that point in the middle of nowhere.

The Rust compiler can't guarantee that the C++ code isn't mutating into an immutable reference.

The Rust compiler can't guarantee that the C++ code isn't holding onto a reference to an object that it will later mutate when Rust thinks it is now the sole owner of that object.

The Rust compiler can't guarantee that the C++ code isn't sending un-Send objects across threads, or that it isn't deallocating objects that it isn't supposed to deallocate.

Rust makes a lot of guarantees about code written in Rust, which is why people are so passionate about it. The Rust compiler feels like having the world's best static code analyzer at your fingertips. It's really nice.

C and C++, on the other hand, are each full of an assortment of powerful footguns. Well-written C or C++ code interops very nicely with Rust, but the problem is that programmers are only human, and humans have been repeatedly shown to make mistakes.

So, calling C++ code is unsafe, and unsafe is a code smell because it is an opportunity for guarantees to be violated, which is undefined behavior.

Ideally, you build an abstraction around the unsafe interop layer that rigorously enforces every requirement that the C or C++ code expects (but that the C and C++ compilers cannot enforce), so that the external code will behave as well as it can.

The Chromium document is right to call their idea controversial. Hiding the unsafety of the C++ code without actually doing anything to protect against malformed calls to that C++ code is a loaded footgun. It might be the right call for this specific case, though.


I disagree; All of the things you say about C++ code also apply to unsafe Rust code. There's an unsafe keyword that allows using that unsafe code, and when you use it, you are asserting to the compiler that "I promise this code is actually being used in a safe way". All this document says is that the C++/Rust boundary should be considered another place where you are asserting "I promise this code is actually being used in a safe way".


This is the right way of viewing it. What unsafe means to a person is fairly irrelevant. What it means to the compiler is 'please insert this code where I have checked the guarantees rather than you'.

Doing this a lot in pure Rust is a bad smell because you usually have other options and you're opening yourself up to risk. In this case the code exists and you already own all that risk so it's perfectly reasonable.


I think the issue is that rust unsafe means "does something sketchy" whereas even perfectly safe C++ would have to be annotated unsafe.


Rust unsafe doesn't mean "does something sketchy". It means "compiler I know what I'm doing". It's equivalent to casting raw C pointers. You are supposed to know what you are doing when doing that, but you will be on your own. It might still be perfectly safe, but the compiler can't tell.


Sure, but it does indicate something likely to be wrong, relative to normal rust code. If every C++ function is unsafe then it becomes less clear where the actually unsafe rust bits are.


C++ has made it very painless to upgrade from C, by being (mostly) a superset. It's so painless that many codebases haven't really upgraded and it's common to see C-isms sprinkled around larger code bases. Some classes written decades ago can survive the years written in horrible outdated style. For some that might be a feature, but for the quality (or safety) of the code it isn't.

Yes, when there is too much unsafe in a Rust codebase then the unsafe label inflates and the safety guarantees aren't as good. But the conclusion of that shouldn't involve changing definitions of what's unsafe and what isn't, but using less unsafe overall.

Rust can interface with C++, but it isn't well suited for too intimate interfacing. That's not where its strengths lie.


I think that's a different goal from what chrome is looking for. It sounds like they want to be able to use their utilities and other code written in C++ from within rust, not integrated on a macro level.


> All of the things you say about C++ code also apply to unsafe Rust code.

Which is why unsafe is considered a code smell, and why people in the Rust community get upset when unsafe is used unnecessarily. That's why some people might find it controversial to hide the unsafety of the C++ code being used here. I've used Rust professionally for years, and I don't remember ever having to write any unsafe code in a professional context apart from FFI, and even then... the glue code to wrap around that unsafe FFI was very strictly written to make things as comfortable as possible for the FFI code and minimize the chance of bad things happening on the other side of the FFI barrier.

However, even then, your statement is not entirely correct. Unsafe Rust is extremely similar to safe Rust, enforcing the vast majority of safety requirements automatically. Unsafe code is very limited in what bad things it can do compared to C++ code.

https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#unsa...

- Dereference a raw pointer

- Call an unsafe function or method

- Access or modify a mutable static variable

- Implement an unsafe trait

- Access fields of unions

Those are the only things unsafe Rust can do that safe Rust can't. It doesn't turn off the borrow checker or anything else. There are a lot of mistakes that can be made in normal C++ code that you can't make in regular, safe Rust.

> All this document says is that the C++/Rust boundary should be considered another place where you are asserting "I promise this code is actually being used in a safe way".

The problem is that C++ compilers are much less capable of enforcing safety (inherently, due to legacy design decisions and backwards compatibility), so you often want a layer of glue code that only allows you to use C++ code in ways that are known to be safe, not just any arbitrary way that the function might be callable.

I already said I agree Chromium's team seems to be making the right call for this specific situation. It's not the right call most of the time. C++ code isn't fungible as being "just as good" as safe Rust code. The unsafety needs to be obvious, just like it is with unsafe Rust, so that you can recognize that the external C++ code (or unsafe Rsut) is a loaded footgun, and to treat it with appropriate care.

To be clear: Rust isn't a perfect language, and you can get the vast majority of the safety benefits by using almost any garbage collected language. What makes Rust awesome is that you can bring the memory safety of garbage collected languages into the realm of consistent performance requirements that previously was inhabited mostly by C and C++. Rust is a nice language, but so are a lot of other languages.


> Those are the only things unsafe Rust can do that safe Rust can't. It doesn't turn off the borrow checker or anything else.

You can do serious damage with those things though. One tactic I have done is to expand the lifetime of a reference by laundering it through a raw pointer, since the function to convert a raw pointer to a reference allows you to choose any lifetime you want.


Yes, but good luck getting that through any form of code review. The unsafe block and the raw pointers or the call to transmute make it pretty obvious that something fishy is going on that’s worth reading carefully. ¯\_/(ツ)\_/¯

Unlike C or C++, where it can sometimes be harder to tell what the lifetime of something should be, or whether you’re extending the lifetime of a reference beyond the point that it is safe.

Unsafe definitely gives you loaded footguns... which for the umpteenth time is why it is considered a code smell. Sometimes you have to write smelly code, but it’s something to be avoided.


> Yes, but good luck getting that through any form of code review.

do you think more than a few percent of the "foundational" OSS projects where we find all the classical CVEs have any code review process ?


> Ideally, you build an abstraction around the unsafe interop layer that rigorously enforces every requirement

I think that’s the reason that doc discusses using cxx, right?

> The Chromium document is right to call their idea controversial. Hiding the unsafety of the C++ code without actually doing anything to protect against malformed calls to that C++ code is a loaded footgun.

I agree with you, but at the same time, I think in this context, single codebase, abstractions built by the teams making the guarantees at the FFI boundaries... it feels like they would meet the requirements here? It definitely is less strict than Rust, but the way I interpret it is that they are saying they will guarantee the C++ is safe behind the abstractions (maybe they can mark the C++ In some way to state this), and that’s not all that different than what we say in general in Rust when we claim an unsafe call is in fact safe when we wrap it in unsafe.

What I mean by mark, is potentially use some annotation in C++ that cxx would only call into when that annotation is present.


> I think that’s the reason that doc discusses using cxx, right?

cxx can only really do as much as the C++ compiler can. It's certainly better than nothing. In my experience, a non-trivial amount of C++ code will have additional requirements on how and when you're allowed to call various functions; requirements that aren't enforced by the compiler.

>> It might be the right call for this specific case, though.

> I agree with you, but at the same time, I think in this context, single codebase, abstractions built by the teams making the guarantees at the FFI boundaries... it feels like they would meet the requirements here? It definitely is less strict than Rust, but the way I interpret it is that they are saying they will guarantee the C++ is safe behind the abstractions (maybe they can mark the C++ In some way to state this), and that’s not all that different than what we say in general in Rust when we claim an unsafe call is in fact safe when we wrap it in unsafe.

That's why I said it might be the right call in this specific situation. Normally, I don't think this is the right approach, but it's probably the sensible thing to do here, especially since it's likely the same team of people on both sides of the FFI boundary.


My point was that if you annotate code that's without the proper Rust guarantees because of cpp calls with a macro, you still can use the literal keyword "unsafe" and have it easily searchable for places where you use it because you have a good Rust internal reason. I.e. not to hide "unsafe" but to make it more clear where it is and because of what.

But they want "No C++ annotations". I believe they should have annotations in the Rust code to make it clear that the Rust/C/Cpp borders are crossed.


Rust does not have a textual substitution pre-processor, so like, you can do this sorta kinda but it would be way more involved.


    macro_rules! cpp_call {
        ($($tt:tt)*) => {
            unsafe { $($tt)* }
        }
    }
Yeah, not pretty, but it works :P


Nature finds its ways.

Seriously though, maybe comment annotation would be better than abusing the macros like that. I though there was a more direct cpp way. (Edit: As in the c preprocessor, not C++.)


> Rust does not have a textual substitution pre-processor

Is that not pretty much what Rust's procedural macros are? [0]

Except for the fact that they do not allow emitting invalid token streams (which is something you don't want anyways), it can do any arbitrary manipulation of source code, but in a way that is integrated with the compiler and less prone to breaking the program.

[0] https://doc.rust-lang.org/reference/procedural-macros.html


A proc macro would still need to be invoked as either

    cpp_call! { fn foo() { ... } }
or

   #[cpp_call] fn foo() { ... }
The former can be written even with a macro_rules macro. Regardless, both macros have to work by parsing the whole input tokentree `fn foo ( ) { ... }` and emitting it back with an `unsafe` prepended. The macro invocation cannot expand to just `unsafe`.


Yes, this is what I was meaning, thank you.


its splitting hairs but not really. Rust's proc macros need to be explicitly invoked using special syntax at the usage site, either by annotating a statement or enclosing a block of tokens. On top of that, they do token replacement (or addition, with derive macros) which means they can only take valid input tokens as an argument (compared to macro_rules which can take arbitrary strings).

Textual substitution is less rigorous. It tells the compiler to replace a string with another, wherever it is seen. There's no restriction that it must be valid token input to be replaced or that it needs to be an explicit usage of the macro.


> Except for the fact that they do not allow emitting invalid token streams (which is something you don't want anyways)

Reminds me of some of stringification abused in the Linux kernel. What looks like a function call passing the literal text param1## as an argument, where it would then trigger a different stringification in a subsequent preprocessor pass that prepended ##foo to the passed argument.


I keep meaning to dive into Rust, but it feels like they've removed so many useful things in the name of safety.

I mean, I get it, these are all things which can be used to very easily cause chaos and shoot yourself in the foot. I look at code that I've written, though, and see so many challenges to writing the same thing in Rust.

I spend 99% of my time in embedded Linux or bare metal, though, so maybe my view point is a bit skewed.

There seem to be very few GUI libraries which can be used in Rust as well. I'll keep checking https://areweguiyet.com/


If you want to use a tool that pastes code fragments in arbitrary places you can. However, I'm not clear on the benefit over something that's aware of its context, or at the very least AST aware.


Even modern C++ has eschewed macros for virtually every use case, the only reason I've had to use them (legitimately) has been conditional compilation and some compiler/platform specific issues.

Like if you're doing #define PI 3.14159 you're asking to get chirped in code review.


Ye, everyone know it is:

    #define PI 3.1415927410125732421875
that is the float closest to pi or that there is the GNU extension USE_FUNKY_DEFINES-something with M_PI for cmath. (I know you meant constexpr:ions).


Long time ago when computers were 32-bit I used to write something like this in C++:

    inline double pi()
    {
        __asm fldpi;
    }
On old enough compilers, this will get you 80-bit version of pi, more precision than even double. Completely useless now, modern compilers no longer emit x87 code.


math.h has M_PI in posix.


The point is that using macros for constants is frowned upon in modern codebases, just like all other use cases for macros that break type safety and the one definition rule.


Yes, I understand the point. But to your comment in a literal sense, if I were doing code review I would probably suggest using M_PI. Which happens to be a macro.

Which actually highlights that many things you want to use for practical purposes are C rather than C++, predate innovations such as const or namespaces, etc. It's possible and common to understand the downsides and still reach for the macro.


Just because we don't have textual substitution doesn't mean we don't have macros. We have a lot of macros!

(It took me a while to write back to you because I am busy writing bare-metal embedded Rust, btw. Including using some macros!)


Back when I was an intern at Mozilla it was a real pain (in my opinion) to call between C++ and Rust. This was before bindgen and cbindgen, and certainly CXX.

Eg if you have a heap allocated thing that is passed between Rust and C++, who frees it? I ended up hacking something together, but it didn't feel right.


>if you have a heap allocated thing that is passed between Rust and C++, who frees it?

Isn't this an issue regardless? Forget crossing the rust/c++ boundary, lets say you pass something to another class in c++, you need to have a contract of who frees what when, right? Maybe I'm not understanding the issue.


Inside both Rust and C++ there are very well developed tools (eg smart pointers) for dealing with memory allocations. These tools were never written to be compatible with each other, so you lose them at the language boundary.


That makes sense. I do 99% C, so any time I even have to think about C++ it unfortunately turns into C with classes rather than C++, so I'm really not as familiar with the smart pointer options.


There are some things that make this better, such as returning pointer types and not returning raw pointers. Returning raw pointers usually means that another function will delete them. I very rarely see pointers handed back from functions being freed caller side


Do you mean references instead of pointers? I'm not familiar of a difference between pointers and raw pointers in C or C++.

C++ allows to be more clear: unique_ptrs allow to move ownership, references mean this is definitely not owned, and the remaining questions are around normal pointers.

With pure C there is however zero distinction. A pointer can mean ownership is transferred, it is meant to be a reference, it could be a static reference to something in constant memory, etc.


I meant pointers versus types such unique_ptr and shared_ptr


You will lost his (*_ptr) magic cuz types match is not compatible. Mem must be free on the allocation side, kind of law.


RAII usually handles all that.


You need to answer "who frees it" in Rust, too, it just doesn't allow you to screw it up.


Surely C++ exceptions and destructors has to be the main issue for interoperation?

I've done to many C++ wrappers with C linkage of C++ libs to call from C, that convert exceptions to error codes and free_obj() wrappers to run destructors.


Chromium does not allow using exceptions, so it may not be that important issue to solve.


passing a heap allocated thing from one c++ dll to another doesn't work. it's far better to define a C interface between binaries of any language and reintroduce language specific semantics in a light client library


> passing a heap allocated thing from one c++ dll to another doesn't work.

this is chromium, everything is built from source with the same compiler, there is zero issue with doing that.

This only does not work when using a DLL built with, say, visual studio 2008 and another built with 2015.


Actually a DLL built with the same version but with either a different c runtime library (e.g. MD or MT) or a different target (release or debug) will have this problem


You must be talking about Microsoft stuff not applicable here.


Not true. GCC broke compatibility with the memory layout of are string at c++11 and passing those between DLLs with versions of GCC on either side of that change will crash.

The standard doesn't specify memory layout or ABI so as a general rule you can't do this. You can get around it by depending on implementation details like what is implicitly done by chromium


Now you are changing the subject. Allocation, which was the subject, happens the same way on either side of gcc's C++11 boundary. The C++11 break was required by the ISO Standard issued in 2011.

The Gcc and libstdc++ maintainers are extremely careful to maintain ABI compatibility between widely-separated releases, where permitted by the Standard. You can happily call between code compiled with gcc-6 and gcc-10, release or debug, optimized or not.

It is possible to deliberately vary ABI with compiler options, but nobody uses those for shared libraries they distribute, for obvious reasons, with the unique exception of choosing pre-C++11 vs post-C++11 ABI.


I guess you're right. I still am wary about depending on this given the current conversation about ABI evolution with C++. And given that lots of languages can interop with C I think building C interfaces between binaries buys options


Hm, I'm a Googler working on the chromecast platform and a Chromium committer who thinks Rust Is Pretty Neat. I'll look internally as well, but who can I talk to about helping out with Rust adoption in the context of the chromium codebase?


Question about the organizational context here. My understanding is that Chromium is an open source project. In practice though, when the document says "we" is it talking about mostly Google engineers? I read the document as indicating that there's a serious possibility of more widespread Rust usage, especially that last sentence

If we become convinced this sort of interoperability is possible, we’ll revisit widespread use of Rust in Chrome, and at that point we plan to work hard to achieve this with robust production-quality solutions.

Would it be wrong to take away from this that in some teams within Google, using Rust in places where C++ would have been used, is something that is being considered seriously?


So building Chromium becomes even more complex. Understanding the build system alone is a major achievement; I even had to build a tool for that: https://github.com/rochus-keller/gntools/. Unfortunately that's only half of the rent; it also needs hundereds of Python scripts; and now also crates will be added.


Fuchsia, uses also "gn" (then ninja) and has rust support too


The post is how they are not using rust, how could you possibly have come to the conclusion that the builds are going to get more complicated?


Because they’re clearly interested in possibly doing so in the future?


> So building Chromium becomes even more complex.

Suck it up and get used to it. Rust is absolutely the future of systems development; every major C or C++ project -- including the kernel -- is going to have a Rust interoperability story, and eventually, major components that implement desirable features written entirely in Rust, so in the interim, until the desirable but currently unrealistic goal of migrating all extant C and C++ code to Rust is achieved, you will need to have both C(++) and Rust tooling (and whatever else) to build the project.


I integrated some basic CEF stuff (enough get to get a basic browser working) into a Rust codebase (https://github.com/hamaluik/cef-simple). It was a massive pain at first, but much easier once you give up on the safety and "idiomacy" of Rust and accept that you have to write in more of C/C++ style. For the parts that can't be written purely in Rust, it ends up being not really any more painful than writing it in C++ anyway.


I hope someday maybe we have a browser completely written in Rust.

What's the point of using Rust in a C++ code base if C++ is the 800lb gorilla as the article implies. If C++ is so important, then just stick to that?!


The strangler pattern (gradually replacing C++ with Rust) is pretty much the only alternative to a https://www.joelonsoftware.com/2000/04/06/things-you-should-... complete rewrite.


Some areas of code are more sensitive than others. Eliminating bugs in areas dealing with encryption, sandboxing, etc may be very beneficial.


Most of the fun stuff your computer is doing right now is the direct result of C and C++ code (browser, kernel, web server, games, etc.). Replacing all that code in one go isn't going to happen. Still, Rust solves some real problems with C++, so we shouldn't ignore it. If we can't replace the world all at once, why not do it piecemeal while targeting the highest value components?


You have to start somewhere. Every C++ component that's replaced by a safe Rust component is a reduction in attack surface.


That was the goal with Servo, but Mozilla axed them https://twitter.com/directhex/status/1293352458308198401


Servo was always a research project around writing a rendering engine and not an attempt to create a whole new browser product.


Webrender would work better as the complete OS interface than as an engine for one browser/app.


> If C++ is so important, then just stick to that?!

A journey of a thousand miles begins with a single step ...

Legacy is a thing. Replacing it piecemeal is the only way to get it replaced.

In addition, you can prioritize pieces. If a particular piece is very likely to be a security problem or has lots of bugs, you can rewrite just that piece into Rust.


If only a certain foundation's corporate half didn't fire its Rust browser developers.


This is all very clever, but it has certain really, really fundamental problems.

1. The main program will remain C++ (or anyway Google's hobbled subset). New code you choose to get done in Rust will be lower-level code, implementing specific new features, or re-implementing low-level stuff too toxic to fix. So, the main task is not Rust calling into C++ code, but C++ calling Rust. That doesn't mean the Rust code won't ever need to call out to utility code, but: You are coding in Rust for an actual, you know, reason, right? Maybe that utility stuff should be RIIR, first? Otherwise what was the point, again?

2. It is all very well to talk about Rust calling C-like functions and virtual functions, but that only takes us up to 1985. We all know (I hope) that the overwhelming majority of useful power in C++ code is in features wholly inaccessible to Rust, even in principle. How will Rust do overload resolution? Inlines? (Also 1985.) Template instantiation? Exception handling? (That takes us to 1992.) Template function overloading and partial specialization? (1998). I could go on, right up to 2020.

There are other fundamental problems, but this seems like enough for now. No sense piling on. At least point 1 makes point 2 less a problem.


I wonder how this compares to Mozilla's approach when they did something similar with Rust, Firefox and servo.


Mozilla's uses a combination of https://github.com/eqrion/cbindgen and https://github.com/rust-lang/rust-bindgen depending on the direction of interop. These tools don't provide the safety guarantees of https://github.com/dtolnay/cxx but the also both predate cxx and C++ doesn't have much in terms of safety guarantees to begin with so the bar is pretty low.


It's also worth mentioning that Mozilla tends not to use a lot of standard C++ types, using nsTArray instead of std::vector or nsA?C?String instead of std::string. This makes the porting of APIs using arrays or structs easier, since Mozilla owns the ABI rather than needing to worry about different standard libraries having different ABIs.


C++ interop with C++ can be a pain. E.g. even with the same version of the same compiler, GCC can have different options enabled that affect the layout of some types.


That is the same in all languages mature enough to have variant options. Rust included.

Therefore, a tendentious distraction from the topic. Chris, I shall expect better from you in the future.


Looks like the Chromium team could also snag a few of them, since servo team is axed

https://twitter.com/ddprrt/status/1293570734271406080


Do they really need to access thousands of C++ API calls from Rust? Doesn't this defeat the purpose of writing safe code in Rust? This smells of "not invented here" which applies to Google projects pretty often.

They should focus on integrating Rust in portions of the API instead of exposing a gigantic unsafe API surface, essentially making all their code unsafe. Unless the Chrome codebase is really such a mess that they can't do anything without exposing this giant API surface. In that case they should rewrite it to... not do that.

"We can't use Rust unless it allows us to write all our code in C++ without showing unsafe warnings"


> Doesn't this defeat the purpose of writing safe code in Rust?

While the Chrome authors will not be protected from memory unsafety in the wrapped C++ APIs, the usage of those APIs in the new code they write on top will be safe, which is better than nothing. Similarly, much of the Rust standard library and many popular crates are "safe" wrappers around unsafe code, like calls to libc, winapi, openssl, etc. If there is a memory unsafety issue in one of those libraries, Rust won't save you.


> Do they really need to access thousands of C++ API calls from Rust?

Yes. It's turtles all the way down. Applying this line of thought to its logical conclusion, it wouldn't be worth it until we have written the OS itself and all firmware in Rust.


Just within the process. Between processes or process and kernel, there are already runtime checks, so Rust isn't contributing safety so much quality.


They should probably look at how mozilla does it. From experience doing something similar, things get simpler when you write your code using handles rather than pointers but that's not how chrome is written.

https://floooh.github.io/2018/06/17/handles-vs-pointers.html


As someone that deals with a chromium-based codebase everyday, i dont think this is a good idea at all.

Unnecessary overcomplication on the codebase, making it more difficult to understand. (Rust and C++ are complex beasts)

The only thing i could think of, is to replace the tools that are mostly in Python.. But this will be a lot of work.

I guess maybe Google wants to employ good Rust enginneers and need to have some "playground" for them.

Swift on the other way, will have native C++ interop, and soon will give the ability to manage the memory ownership the same way C++ and Rust does (not just defaulting to ref-count)..

Once Swift have those properties we will have one more good contender in the same arena as C++, Rust and Zig.


I doubt the point is to offer people a playground but rather to find a way to write new code in a language that is memory safe by default. Browsers are notoriously plagued by bugs related to memory safety so there’s quite a lot of motivation for at least considering this path.


That is the problem with default narratives, they dont adapt well to every case.

Chromium codebase is a massive codebase. It works, its efficient and fast, its sophisticated and complex, its well tested, had all sort of bugs that was taken out of them. Its really well written C++ code with modern ownership semantics. So a lot of mistakes that are used as boogeyman to convince people to use Rust are barely problems you really face.

And no matter what wonders Rust promisses, a lot of bugs would get back there in case of rewriting things.

Rust can make a very good point when the thing to be rewritten is in C (if is not a billion dollar codebase like Linux). But with big codebases, well written and modern C++ it doesnt make sense at all.

I get it why someone would start a new project in Rust though.. but the things dont add up when we talk about big codebases already coded with good C++ practices.


> So a lot of mistakes that are used as boogeyman to convince people to use Rust are barely problems you really face.

https://bugs.chromium.org/p/project-zero/issues/list?q=produ...

This year alone saw 2 issues from Project Zero in Chrome that would have been prevented by Rust - both OOB accesses, one with a helping of data racing.

Chromium is an incredibly security-sensitive piece of software, and that really is an excellent fit for Rust. It's not a boogeyman argument and the Chromium team themselves are the ones pursuing it, it's not being forced on them.


I have no doubt that Rust could save some sort of bugs. If you read all the comment i´ve made, you will see that we are talking about a massive codebase already coded in C++.

Giving the time it is alive, all the engineering hours, investiments, tools, etc.. most of the bugs that Rust could have prevented right on the beggining are already covered (Chrome also uses fuzzers and static analysis tools to automate C++ code)

> This year alone saw 2 issues from Project Zero in Chrome that would have been prevented by Rust

Thanks for making my point even clearer. If you really knew the size of the codebase, having only 2 issues on Project Zero is kind of a compliment to Chrome.

By the way, it was yesterday, that i´ve saw a research with fuzzers applied in C, C++ and Rust codebases.

In that research, Rust had the same overall count of bugs and even some 'zero day' sort of ones not any different from C++ and C codebases (maybe just better than the C one).

Its a pitty i can find where it is (I´ve saw it on Twitter, but its pretty recent).

> Chromium is an incredibly security-sensitive piece of software, and that really is an excellent fit for Rust. It's not a boogeyman argument and the Chromium team themselves are the ones pursuing it, it's not being forced on them.

A Chrome started from zero in 2020? probably.. but thats not the reality here. Millions of lines of C++ code, and its not just a matter of language, but the knowledge and people with the know-how to write those pieces..

Thats why im saying is not worth it..


> In that research, Rust had the same overall count of bugs and even some 'zero day' sort of ones not any different from C++ and C codebases (maybe just better than the C one).

That's actually not entirely true. One of the problems was the program taking too long to compete due to suboptimal algorithm. The GitHub repo showed that the tests were run using debug version of the Rust binary. It worked fine when people tried to reproduce using the release version.

For the other two problems, not many details were given, but both programs panicked immediately with the given input. That's still better than any kind of undefined behaviour or silent error.


> most of the bugs that Rust could have prevented right on the beggining are already covered (Chrome also uses fuzzers and static analysis tools to automate C++ code)

This isn't even close to true...

https://chromereleases.googleblog.com/2020/08/stable-channel...

Here's one from just this month.

Here's another from just this month with another fifteen issues: https://chromereleases.googleblog.com/2020/08/stable-channel...

There are hundreds a year like this. I could find many, many, many more.


You also have to consider the huge investment that testing takes. Not only person days spent writing tests, but also infrastructure costs for running fuzzers, huge test suites and so on. And although the code base is already there and tested, it constantly changes, so there is no way to just incrementally test it. At some point you have to make some advances in the way you approach software development, not everything can be handled by brute force.


It would cost much more to rewrite in Rust.

Again thats why im saying that if the project would start now in 2020, all of those would be pretty good points. Right now is too late for that.

As another example, you can get just the V8 VM for instance. For start, with the ammount of solid code out there it would be nuts to talk over a rewrite..

Now imagine if we add to that, that the knowledge on JIT compilers is so difficult to grasp, that you would also need to have those same great engineers in the new language/platform you are planning the rewrite.

Giving the age of Rust, and giving you already have a bad time of finding those in the much older C++, imagine the time it takes to have the same quality people in Rust.

I know a lot of people are bully about Rust, but in reality things are not that simple.

(On the bright side, is not that hard to taught a C++ engineer to code in Rust)


If you care about security, it is much easier to write non-exploitable code in Rust than in C++, and you can hire accordingly.

Actually, the people who are really experienced at writing secure code in C++ understand that they can't do it reliably, and an increasing number of them (like me) would much rather write Rust code instead for that reason. So for secure code the hiring advantage is tilting away from C++ towards Rust.


Yet once the Chromium team goes down that Rust to C++ FFI route, they will encounter the same old bugs outlined in this paper. [0] They might as well fork Servo and create their own engine from that, but even that is still a prototype worked on for years with zero C++ at least.

It's too early to see how the Chromium team will begin to work on this given it's only a plan, but maybe the unsafety and security issues are enough motivation to actually use Rust to C++ interop or a rewrite. But once again, this has been tried by some other projects and by just looking at the effort of a rewrite, they later concluded that it is not worth it. [1] [2]

Even better, just improve Servo instead. But I guess they would rather create their own fork, just like they did with WebKit.

[0] https://arxiv.org/pdf/2003.03296.pdf

[1] https://blogs.dust3d.org/2019/03/13/why-i-rewrote-the-mesh-g...

[2] http://way-cooler.org/blog/2019/04/29/rewriting-way-cooler-i...


None of this logic follows. Paper [0] categorizes bugs in unsafe Rust code. "There will be some bugs in FFI code" does not imply "no benefit to using Rust if there is C++ FFI". Many projects, including Firefox, have found Rust is really helpful even if you have C++ FFI.


By the way, there's a lot of questions about the methodology of that paper: https://www.reddit.com/r/rust/comments/icaf19/we_tested_the_...


To be fair Steve, i didnt use this as to invalidate the assumptions on Rust. Just that we need to be more carefull to just take things for granted.

Im not even taken this research for granted, and are waiting for further work to really prove the points, or even going against them.

Im just using as a cautionary tale for people going too fast on some assumptions, and not taking care to study sample by sample.

I have no doubt that Rust have a great future ahead of it. But it will take more time and lines of code to turn all of those wishes people have into reality.


Absolutely. I am very interested to see how everything plays out in the future.


> So a lot of mistakes that are used as boogeyman to convince people to use Rust are barely problems you really face.

The Chromium team does not believe you. https://www.chromium.org/Home/chromium-security/memory-safet...


> So a lot of mistakes that are used as boogeyman to convince people to use Rust are barely problems you really face.

To elaborate on the other commenter, the majority of bugs found in chromium are bugs that rust would avoid (memory safety issues that rust makes ~impossible).


If it was written from the start in Rust, i have no doubts. But thats not the reality being faced here.

Right now the Chromium codebase have less bugs than if it was written in Rust, because of how many hours and efforts that were spent on the codebase to make it work smoothly.

Also the particular engineers with particular know-hows work with a particular tool. Its not easy to find them especially if you are forcing to use a new language in a giving project. It will take years and even decades to have the same quality people you already have in C and/or C++ in Rust.

So it will take time to have an engineer that can make, lets say, a multi-process compositor in Rust. What about many people with that particular special knowledge that take years to be good at.

So in real life its not just a matter of a language being a better fit. Theres a lot of other vectors to take into account.

https://www.joelonsoftware.com/2000/04/06/things-you-should-...


The security benefits of Rust outweigh those concerns. Almost all C++ code in Chromium is security sensitive. Even if it is sandboxed it can be part of an exploit chain to get to a sandbox escape. Adopting more secure languages should be a long term goal of the project.


Rust and Swift are not in the same arena as C++ and Zig. The former offer memory safety and the latter don't --- a critical distinction, since the main reason Rust exists is to provide memory safety in contexts where C++ has traditionally been dominant.


Thats because you have divided the languages by properties and i´ve originally divided them over the class of software that can be written in those languages.

More as "system language" category, where you can code OS´s, JIT compilers, Browsers or any time/memory sensitive kind of software. Thats why they were packed together.

Classically, those sort of things could be only coded in C and C++ as the attempts in doing them in "higher level" languages mostly failed.


TIL Rust doesn't have function overloading. But actually not sure whether this is a good or bad thing.


I’ve been using it since pre 1.0 and I couldn’t tell you either. (Traits get you most of the way there but you’d see them exist for no other purpose which is just boilerplate imho.)


What's so good about function overloading in C++? I've never used it in any C++ I've written.


Looks way too complicated. I think the reason is rust is not good at OOP. Compare with my C# to C++ interop library, it can easily pass arbitrary complicated types both ways: https://github.com/Const-me/ComLightInterop


The problems listed aren't the oop, they are the memory and thread safety rules of rust, that prevent all the null pointer dereferencing and leaky memory that a "dumb" C++ wrapper allows.


Here's a quote from the article:

> We think the hardest part of this is imagining a safe way to pass types between Rust and C++. That requires auto-generated shim code on both the Rust and C++ side.

I'll be very surprised if they'll be able to make a production-quality solution. The languages are just too different, and Rust's OOP support is one of these issues.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: