The rust's ownership mechanism has many constraints on the syntax, will it cause some limitations in the implementation of the function? Compared with C/C++

Always feel that it has some limitations when it comes to implementing complex functions.

Do you have an example of such a limitation you can show us?

Because without that I will believe that anything one can achieve in C++ can be achieved in Rust.

In fact I will argue one can do things in Rust one cannot do in C.

One important difference between Rust and C++ in terms of "expressibility" is that C++ allows custom code within the "copy constructor" and "move constructor". And the way they work is that they already know where they are writing to (so "copy constructor" is not clone(&Self) -> Self but rather clone_from(this: &mut MaybeUninit<Self>, from: &Self) and of course there is no overload / custom code hook for Rust moves).

This results in it being quite easy in C++ to have self-referential structs that don't get invalidated when created or moved (as the custom code hook then "fixes the internal pointers"), whereas this is only doable in Rust through very heavy closure-based style.


Note that I am not advocating that it should be possible to overload moves in Rust; I am just stating that not having them has this (minor) drawback.

2 Likes

Hi,
I can share my experience as a c++ dev from 9-10 years now, mainly in game dev. They're is no specific limitation using Rust, the main "problem" is rust require to "think" more before coding, because the compiler is not very permissive, where GCC Clang or MSC will allow to compile buggy code it's more complicated with rust. This is a balance to have between debugging more (c++) and think more (rust).
I know that you can think in C++ :wink: but nobody spot all bugs when you write code. Rust compiler help a lot by imposing the borrow checker.

3 Likes

Yes, certainly. Just like having any type system at all imposes limitations on the kind of code you are allowed to write. Because that is the whole point of a type system, and ownership rules in Rust are no different.

Most of these limitations are intentional and good, as they enforce sane structure and consequently prevent large classes of errors.

Some of the limitations are unintentional side effects and are bad as they prevent certain pieces of sound code from being accepted.

In my (and many others') opinion, the advantages of enforced structure and rules far, far outweigh the small annoyances.

7 Likes

From some theoretical point of view Rust and C++ are both equivalent to a Universal Turing Machine. So whatever can be done in one can be done in the other. (Ignoring that infinite memory space of the Turing Machine)

From a practical point of view, the fact that the Rust compiler and other significant programs have been written in Rust is a convincing demonstration that Rust can do what ever we want. Or at least whatever I might want.

Further....

The theoretical view in my first paragraph is false. C++ is not a Turing Machine. When Turing defined his machine he specified that all unused symbol positions on the memory tape were initialized to a known value "blank". It's a mathematical construct, of course you have to pin everything down before you can make any deductions from it. This requirement for a known initial memory state is not present in C++.

As such we see that the overwhelming number of possible input texts to a C++ compiler, that do actually compile, are in fact of undefined behavior. It is vanishingly improbable that a programmer or group of programmers can find a program without UB in that huge space of possibilities above a some not very big size of program.

So, there is one huge significant difference. Unlike C++ it's impossible to write a random number generator in Rust (At least without using "unsafe"). I mean, where would one get all that undefined behavior from? :slight_smile:

In C++, accessing uninitialized memory is also UB. You can't write a reliable and portable PRNG in conforming C++ by reading uninitialized memory. It's just that in C++ it's easier to end up with uninitialized memory and UB than it is in Rust.

Yes, that what I said.

I was not talking about pseudo random number generators, I meant actual random. As in accessing arrays out of bounds, use after free, overflows and so on producing results we cannot determine. At least not by referring to the C++ language specification.

My argument is that it's not just easier to end up with undefined behavior in C++ it is almost 100% certain.

Consider all the possible arrangements of characters one could type into ones editor to create a program. Say one million characters in your program. That is a number far bigger than the number of elementary particles in the universe.

Now lets remove all those permutations that the compiler rejects. That still leaves us with an overwhelmingly huge number of possible programs that will actually load and try to run.

Of those, the number that actually produce results that are defined by the C++ specification is vanishingly small. The probability of programmers ever finding a program without UB is approximately zero. Think monkeys with type writers and hoping to get Shakespeare.

In short C++ is not a Turing machine and nothing predictable.

Is it actually possible to end up with UB in Rust, without using unsafe?

That is a very sharp jump from "C++ permits uninitialized memory" and not a convincing conclusion, also relying on the false premise that C++ code is uniformly distributed in terms of characters. That is pretty much not the case. Programmers do not produce programs by typing random characters.

(This, however, is getting off-topic, as it does not concern the original question, so I'm not interested in continuing it here.)

2 Likes

H2CO3,

Yes it is of course a big jump. Based on some simplistic and suspect statistics. Sorry, I'm prone to such grand simplifications when trying to illustrate a point. However those stats are somewhat correlated with the statistics produced by MS and others re: the number of CVE's they can trace back to memory unsafety over the years. Running at about 60 to 70%.

I do have a tenuous connection to the OP's question here mind.

Given that Rust is an implementation of a Turing machine and that C++ is not, my answer to the question is that I posit that Rust is the more expressively powerful language in a strict mathematical sense.

One need not worry about it's perceived limitations when wanting to implement any function.

As a mathematician I regret to inform you that in a strict mathematical sense, C++ is just as much an implementation of a turing machine as Rust (and Python 3) is.

4 Likes

I challenge that assertion.

The Turing Machine has a rigorous definition, including:

"A tape divided into cells, one next to the other. Each cell contains a symbol from some finite alphabet. The alphabet contains a special blank symbol (here written as '0') and one or more other symbols. The tape is assumed to be arbitrarily extendable to the left and to the right, i.e., the Turing machine is always supplied with as much tape as it needs for its computation. Cells that have not been written before are assumed to be filled with the blank symbol."

Basically that says it has a memory all of which is initialized to a known symbol.

Conversely uninitialized variables in C++ are specifically stated to be undefined. Essentially they are random numbers.

A C++ program like:

    int x; 
    std::cout << x;

has no defined output. Likewise for out of bounds array access, dereferenceing invalid pointers etc, etc,

Ergo, I conclude C++ is not a Turing Machine. More of a random number generator.

Heck, C++ does not even specify the size of an int so even the symbol alphabet in use is not defined!

To make your point, could show a Turing machine that implements the above C++ program?

Rust I'm not totally sure about but it seems to me that with it's uninitialized use checking, bounds checking and overflow checking etc is actually a Turing Machine.

Aside: Yes I know, modern compilers try to check for these undefined conditions and warn about them, that is not written into the language specification and cannot be relied upon.

That's backward. With this "uninitialized memory RNG", Turing machine can't emulate C++ but C++ can emulate Turing machine. That means following your logic C++ is more expressively powerful language than Rust in a "strict mathematical sense".

3 Likes

It's written into the spec that accessing uninitialized values is UB, and thus you don't have a valid C++ program when you do so. This is very similar to Rust, only Rust is better at stopping you from doing it.

1 Like

I don't subscribe to that train of thought.

I agree that a carefully written program in C++ can emulate a Turing Machine. In general though a C++ program cannot even emulate any other C++ program. For example you cannot emulate on your machine with your compiler and operating system what happens with C++ on my machine and my compiler and operating system.

One could only argue that C++ is more power full than a Turing Machine if you consider generating random results desirable. (Which is often true but then were are not talking about deterministic systems anymore).

Yes indeed. That is the point I am making. C++ only becomes a deterministic Turing machine if you add the human programmer who checks that no UB's are encountered.

I may be wrong but as far as I understand so far all possible input to a Rust compiler has a deterministic output. Should that happen to be a successful compilation the resulting program has a deterministic behavior (assuming overflow checks are on) for all it's possible input.

Or do you know of a Rust source that compiles to something that produces undefined results?

I would say that is very dissimilar to C++.

1 Like

Just add some unsafe, and you'll easily show it yourself.

2 Likes

Ha, touché!

Using "unsafe" would certainly do it of course. But then all pretense of being a Turing Machine is out the window.

I predict that in the coming years the C++ guys, inspired by Rust, will dream up a way to add ownership and a borrow checker along with other safety features to C++. No doubt with some more horribly cryptic syntax to support it.

Oh, I forgot, Herb Sutter is already working on such a proposal: https://herbsutter.com/2018/09/20/lifetime-profile-v1-0-posted/

1 Like

Practicalities has made Rust less "pure" than it could have been - Rust is built upon a compiler - Llvm - that's also used for C++. A lot of the underpinnings are the same, some validity and soundness rules are derived from what Llvm allows and so on.

We have a set of Rules that Rust programs must follow - whenever written in safe or unsafe code blocks, and there we have some similarity to C++ in what's allowed.

We can in fact cheat a bit and use the Rust bug tracker, and we should be able to find one of the few bugs still open which deal with places where Rust programs have indeterminate behaviour and/or output, without unsafe! The issues around special cases in int ←→ float casts are maybe still open.

2 Likes

Could you elaborate on that for me?

C++ has rules that a programmer must follow so as not cause undefined behavior and create a random number generator, like "do not make out of bounds access to arrays" and so on. But often if one gets those wrong there is no enforcement by the compiler or at run time. One can often get expected results when testing. The first one may know about the rule violation is when the bug reports come in. Even then one has no idea which rule was broken where.

What rules are there that I must follow when writing Rust programs, without "unsafe", which ensure that Rust does not produce indeterminate results? Other than those that the compiler or overflow checker tells me about?

Ah yes, bugs in the compiler implementation. Well, one hopes they get fixed rather than being a specified feature of the language.

Sure! The turing machine that does nothing is a correct implementation of that program.

I find it quite funny that you're giving programs with undefined behaviour as an example of things you cannot simulate with a turing machine. Programs that are valid can do all sorts of crazy shenanigans such as reading the contents of a file, which is impossible from a turing machine, whereas every turing machine is a correct implementation of a C++ program with undefined behaviour.

Undefined behaviour is definitely not a random number generator. See here for more details.

2 Likes