12
$\begingroup$

Rust has a number of memory safety features. Is it possible to extend or enhance C or C++ to also provide similar memory safety features instead of using workarounds such as the Valgrind tool suite?

$\endgroup$
6
  • 1
    $\begingroup$ In C++ SmartPtr and SharedPtr already exists which has similar semantics to rust ownership rules $\endgroup$
    – mousetail
    Commented May 16, 2023 at 17:44
  • $\begingroup$ C++ has been working toward this for years. Here's a talk from several years ago about their efforts: youtu.be/hEx5DNLWGgA $\endgroup$ Commented May 16, 2023 at 17:45
  • 1
    $\begingroup$ @mousetail IIRC SharedPtr and SmartPtr don't have checks for not borrowing the same object as mutable more than once simultaneously. $\endgroup$
    – kouta-kun
    Commented May 16, 2023 at 17:46
  • $\begingroup$ @kouta-kun true, they do attempt to avoid use after free and memory leaks in a similar way to rust but you are correct there is no attempt to prevent aliasing $\endgroup$
    – mousetail
    Commented May 16, 2023 at 17:49
  • 1
    $\begingroup$ Rust got the idea from C-alike languages such as Cyclone. en.wikipedia.org/wiki/Cyclone_(programming_language) $\endgroup$
    – Pseudonym
    Commented Sep 4, 2023 at 1:58

3 Answers 3

13
$\begingroup$

I think this answer depends a lot on what you mean by "extend C" here! If you're looking for a language in which Rust ownership exists and C libraries can be used natively, that's basically what Rust already provides. If you're imagining something like a new C compiler that gives you Rust-style errors for memory unsafe code, but still compiles existing idiomatic C without complaint, then I'm afraid that's not technically possible under a Rust-style ownership system. This is fundamental; Rust limits the shape of representable datastructures in a way that C does not.

But if you're willing to accept some limits, then "extending C/C++" to be more Rust-like is totally possible! There are even a few papers showing ways to build a type system with Rust's guarantees in which idiomatic datastructures are easier to represent, or the base language remains more C-like. A lot of these papers wound up influencing the design of Rust!

$\endgroup$
9
$\begingroup$

You can add annotations to C syntax to encode and verify arbitrary properties, including ownership.

See VeriFast examples, like this one which uses multiple threads and annotations to verify there are no data races.

I don't think there are any examples of using VeriFast to model ownership and borrowing semantics in C, and I doubt VeriFast would be effective at doing so. However, you can certainly make a language similar to verifast which explicitly adds annotations to C parameters and values to define whether they are "owned" or "borrowed".

The annotations don't have to be just comments either, you could define a DSL which alters C's syntax directly. You can even start replacing C syntax and changing the semantics, but at a certain point, you're basically making a new language which is no longer "C" (unsafe Rust looks very similar to Cthough it's not).

The problem with this approach is that once you add ownership to a language which wasn't designed with it in mind, you get issues. Because you have to annotate standard library and all external library functions with ownership, and these libraries sometimes spuriously borrow and copy data because designed with ownership in mind. Among other issues.

I also found this article on using move-only types to emulate the Rust borrow checker in C++. So I guess if adding move-only types would be the first.

$\endgroup$
3
$\begingroup$

There is more to rusts safety model than Ownership.

For rust safety to work, it is important that the safety invariants are upheld. In safe rust these invariants are upheld automatically. In unsafe rust, and in non-rust code that interacts with rust, it is your responsibility as the programmer to uphold them.

An important part of rust's safety model is that you are allowed to have sharing or mutability but normally not both at once. So if you have a reference to an object you can be confident that the object will remain in a stable state for as long as the reference is valid.

This is what allows, for example, you to safely derive a str from a reference to a String, or to derive a reference to one or more of a Vec's elements from a reference to the Vec. The compiler knows that as long as the reference to the String/Vec is held, not only will the String/Vec continue to exist, but that it's internal buffer will remain stable.

Converting a raw pointer to a rust reference is (somewhat obviously) an unsafe operation. The compiler has no idea where that pointer came from, what it's lifetime will be or whether there are aliasing issues with it. Less obviously, accessing a mutable global variable is also an unsafe operation, because the compiler has no idea what other functions may be running simultaneously and trying to access said global variable.

Of course sometimes shared mutability is needed, rust implements this through a concept called "interior mutability". A type that implements "interior mutability" can be mutated through a regular (normally immutable) reference. The catch is that the type itself then becomes responsible for ensuring that sharing rules are followed regarding the interior data.

Another important aspect of rust's safety model is thread safety. Rust types can declare aspects of their thread-safety through the "send" and "sync" traits, though in most cases the compiler works this out automatically from the constituent types.

Another important aspect of rust's safety model is encapsulation. To do their job safely, many types need to carefully control their internal state. For example a smart pointer may be represented as a structure containing a raw pointer, the safety of the smart pointer relies on knowing that said raw pointer points to a valid combination of data and control block.


Rust has pretty much all the features C has (the only exception that immediately springs to mind is variable length stack allocations). Indeed there exists at least one automatic C to rust translation tool. I don't see any fundamental reason that the language features of rust could not be added as extensions to C. I do however see a number of practical difficulties.

  1. How do you distinguish "safe" code from "unsafe". If existing code has to keep compiling then "unsafe" has to be the default but that kind of goes against the idea that unsafe code should be the exception not the rule. Rust also distinguishes between functions that performs unsafe operations internally, but are (supposed to be) safe to call and functions that expose an unsafe interface. How will this distinction work in your language.
  2. If you are extending C++, then ditto for types. This is less of an issue for C because C types are all plain old data with no encapsulation in the first place.
  3. How will encapsulation work. Rust has a modules/crates system for this, will you copy that in your new language, or design something new. I don't think the C++ system of header files and manual linking is good from a safety point of view.
  4. The safety model falls apart if it's preconditions are violated, so to get the maximum benefit large chunks of code should be rewritten in a safe way, with calls between safe and unsafe code carefully audited to ensure that they do not violate the safety model.
  5. A safe standard library is needed for safe code to use. This will parallel large parts of the C standard library. How do you avoid/manage the name conflicts this will cause.
  6. How will arrays work in your language. In C/C++ arrays are not indexed directly, instead an array "decays" to a raw pointer, and array indexing is a shortcut for pointer arithmetic. That obviously isn't acceptable in a safe programming environment, how will you replace it while maintaining compatibility? Will arrays behave differently depending on whether you are currently in "safe" or "unsafe" code?

My feeling is that by the time you have addressed all these issues, your language will effectively be "two languages in a trenchcoat", sharing basic syntax but little else.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .