> 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).
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.
This is well-defined, as being dropped in the order that they are declared.
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.
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.
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.
“We are happy to empower our younger colleagues. We only ask they have 25 years of experience.”
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.
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. :)
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'
"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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
This is good documentation on the safety of the library: https://github.com/dtolnay/cxx#safety
#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.
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.
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?!
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.
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.
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.
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.
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.
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"
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.
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.
> 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.
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?
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.
https://floooh.github.io/2018/06/17/handles-vs-pointers.html