GrantMoyer 3 days ago

While programming in Rust, I've never thought to myself, "man, this would be so much easier to express in C++". I've plenty of times thought the reverse while programming in C++ though.

Edit: except when interfacing with C APIs.

3
throwawayffffas 2 days ago

I have had the exact opposite experience.

bowsamic 3 days ago

Then you must be avoiding situations that traditionally use OOP

zozbot234 3 days ago

Most kinds of OOP can be expressed idiomatically in Rust. The big exception is implementation inheritance, which is highly discouraged in modern code anyway due to its complex and unintuitive semantics. (Specifically, its reliance on "open recursion", and the related "fragile base class" problem)

galangalalgol 3 days ago

People often say that modern c++ doesn't have the problems needing a solution like rust. Ironically that means people who write modern c++ haven't had any aramp up time needed when joining our rust projects. They were already doing things the right way. At least mostly. But now they don't have to worry about that one person who seems to be trying to trick the static analysis tools on purpose.

int_19h 2 days ago

Anything that involves object graphs (as opposed to trees) is a pain in Rust.

zozbot234 2 days ago

True, but not in a way that wouldn't be just as painful in C++.

int_19h 2 days ago

In Rust, the de facto standard advice for such cases seems to be, "just use indices into an array instead of references".

While this is sometimes done in C++ as well for various reasons, it's certainly not the default pattern there. If you have two things that need to point to each other, you just do that.

empath75 2 days ago

> While this is sometimes done in C++ as well for various reasons, it's certainly not the default pattern there. If you have two things that need to point to each other, you just do that.

And then you have to handle all the subtle memory bugs that you've introduced by doing that.

int_19h 2 days ago

I'm not arguing that there isn't a gain here, but GP's original assertion was that

> While programming in Rust, I've never thought to myself, "man, this would be so much easier to express in C++".

This is a concrete example of something that is much easier to express in C++. And, sure, you do pay the tax for that (although I will also dispute the notion that it is impossible to write C++ without memory bugs; it's just hard).

LinXitoW 2 days ago

I guess this is a semantics argument, but I assume they mean to express the same thing with same (or reasonably same) security guarantees. After all, the security and "bug freeness" is part of what they are expressing. If you attempt to create something reasonably similar to Rust, you do suddenly need a lot of complex checking code and maybe tests for things that were trivial in Rust (because the compiler does the tests for you).

simonask 2 days ago

Is it really easy to express if the straightforward way is buggy and error-prone?

People think C++ is expressive because they think they are allowed to do a lot of things that they aren't, in fact, allowed to do in C++.

kkert 3 days ago

This is interesting because i'm writing quite a bit of embedded Rust, and i always run into limitations of very barebones const generics. I always wish they'd have half the expressiveness of C++ constexpr and templates.

Win some, lose some though, as the overall development workflow is lightyears ahead of C++, mostly due to tooling

badmintonbaseba 3 days ago

The expressiveness of const generics (NTTPs) in C++ wouldn't go away if it adopted lifetime annotations and "safe" scopes. It's entirely orthogonal.

Rust decided to have more restrictive generic programming, with the benefit of early diagnostic of mistakes in generic code. C++ defers that detection to instantiation, which allows the generics to be more expressive, but it's a tradeoff. But this is an entirely different design decision to lifetime tracking.

zozbot234 3 days ago

Rust generics are not intended as a one-to-one replacement for C++ templates. Most complex cases of template-level programming would be addressed with macros (possibly proc macros) in Rust.

galangalalgol 3 days ago

Const generic expressions are still being worked. They are what is blocking portable simd. They are also a much cleaner way to implement things like matrix operations or really anything where a function with two or more arguments of one or more types returns things that have types that are a combination or modification of the input types.

zozbot234 3 days ago

The problem AIUI is that "const generic expressions" in full generality are as powerful as dependent types. It's not clear to me that the Rust folks will want to open that particular can of worms.

galangalalgol 3 days ago

I thought dependent types were types that depended on a value? What they are proposing are types that depend on types or compile time constants.

zozbot234 3 days ago

The problem is combining the "const generic" and "expression" part. If your "compile time constants" can actually be complex expressions, you arguably end up with the same kind of generality as dependent types.

This is true even for expressions that are only evaluated in a compile-time context, since dependently-typed languages do "everything" at compile time anyway, they don't have a phase distinction where you can talk about "runtime" being separate.

galangalalgol 3 days ago

Ah, yeah! I get it now. So c++ is a dependently typed language. That is hilarious. I want lisp syntax in c++29. That said, too many features are blocked on const generic expressions, so I think they are going to have to bite that off. There is already talk about migrating proceduralacros to be something more like normal rust, this moght fit in with that.

Rusky 3 days ago

C++ is not a dependently typed language, for the same reason that templates do not emit errors until after they are instantiated. All non-type template parameters get fully evaluated at instantiation time so they can be checked concretely.

A truly dependently typed language performs these checks before instantiation time, by evaluating those expressions abstractly. Code that is polymorphic over values is checked for all possible instantiations, and thus its types can actually depend on values that will not be known until runtime.

The classic example is a dynamic array whose type includes its size- you can write something like `concat(vector<int, N>, vector<int, M>) -> vector<int, N + M>` and call this on e.g. arrays you have read from a file or over the network. The compiler doesn't care what N and M are, exactly- it only cares that `concat` always produces a result with the length `N + M`.

groos 2 days ago

I'm not sure what "dependently typed" means but in C++20 and beyond, concepts allow templates to constrain their parameters and issue errors for the templates when they're specialized, before the actual instantiation happens. E.g., a function template with constraints can issue errors if the template arguments (either explicit or deduced from the call-site) don't satisfy the constraints, before the template body is compiled. This was not the case before C++20, where some errors could be issued only upon instantiation. With C++20, in theory, no template needs to be instantiated to validate the template arguments if constraints are provided to check them at specialization-time.

Rusky 2 days ago

This is the wrong side of the API to make C++20 dependently typed. Concepts let the compiler report errors at the instantiation site of a template, but they don't do anything to let the compiler report errors with the template definition itself (again before instantiation time).

To be clear this distinction is not unique to dependent types, either. Most languages with some form of generics or polymorphism check the definition of the generic function/type/etc against the constraints, so the compiler can report errors before it ever sees any instantiations. This just also happens to be a prerequisite to consider something "dependently typed."

zozbot234 2 days ago

> performs these checks before instantiation time

Notably Rust type-based generics do this, a key difference wrt. C++ templates. (You can use macros if you want checks after instantiation, of course.)

galangalalgol 3 days ago

In c++ it does care what N and M are at compile time, at least the optimizer does for autovectorization and unrolling. Would that not be the case with const generic expressions?

Rusky 2 days ago

The question of whether a language is dependently typed only has to do with how type checking is done. The optimizer doesn't come into play until later, so whether it uses the information is unrelated to whether the language is dependently typed.

galangalalgol 2 days ago

Ok, I think I understand now, but is it really dependently typed just because it symbolically verified it can work with any N and M? Because it will only generate code for the instantiations that get used at compile time.

Rusky 1 day ago

Is what really dependently typed? I'm saying C++ is not dependently typed, because it doesn't do any symbolic verification of N and M.

galangalalgol 1 day ago

If rust did add const generic expressions I mean. It still would only generate code for the used instantiations.

Rusky 1 day ago

Ah, I wasn't really talking about Rust.

Rust already does have some level of const generic expressions, but they are indeed only possible to instantiate with values known at compile time, like C++.

The difficulty of type checking them symbolically still applies regardless of how they're instantiated, but OTOH it doesn't look like Rust is really trying to go that direction.

jcelerier 2 days ago

the only thing needed here is to be able to lift N & M from run-time to the type system (which in C++ as it stands exists only at compile-time). For "small" values of N&M that's doable with switches and instantiations for instance.

Rusky 1 day ago

The point of dependent types is to check these uses of N and M at compile time symbolically, for all possible values, without having to "lift" their actual concrete values to compile time.

Typical implementations of dependent types do not generate a separate copy of a function for every instantiation, the way C++ does, so they simply do not need the concrete values in the same way.

smilekzs 2 days ago

> as the overall development workflow is lightyears ahead of C++, mostly due to tooling

My experience has been the other way around. Eclipse-based IDEs from NXP, TI, ST all have out-of-the-box usable tooling integration:

- MCU pinout and configuration codegen

- no need to manually fiddle with linker scripts

- static stack and code size analyzers (very helpful for fitting stuff in low-cost MCUs)

- stable JTAG-based debugging with:

  - peripheral registers view (with bitfield definitions)

  - RTOS threads view (run status, blocked on which resources, ...)
And yes, these are important enough for me to put up with Eclipse and pre-modern C/C++. I really want to write Rust for embedded but struggling with the tooling all the time didn't help.

afdbcreid 3 days ago

That's actually quite interesting because this is not an inherent limitation of Rust, and it is definitely planned to be improved. And AFAIK, today (as opposed to last years) it is even being actively worked on!