D 2.069.0 released, compiler automatically ported from C++ to D
dlang.org
dlang.org
Niklaus Wirth did write many of his compilers in the original language (kind of).
He would write down on paper the code, using the bootstrap version 0 code style, as it was supposed to be.
Then he would manually translate that code into Assembly.
So when the compiler for the basic language was working, he could use the same code again, without additional efforts and relying on third party languages.
Specially important back in the day where each computer system had its own systems programming language or dialect of an existing one.
EDIT: typo, time was missing
But I (and many other compiler devs) thought back in the early 80's that Ada was so complex it was unimplementable. Today that thought seems charmingly naive.
Work on LISP/Scheme, Julia, REBOL, and so on show that even complex constructs can be built with macros, etc on simple ones which can be done Wirth-style as in Lilith/Oberon, my style [1], or by hand like pjmlp said. Actually, the Scheme stuff went down to synthesizing hardware from interpreters written in Scheme and another person did that with a Oberon-based HDL. So, it can go much further. :)
In addition, the D authors are generally opposed to disciplined approaches, e.g. type classes, region-based memory management, which are being added to C++. Especially regarding the former, while "design by introspection" may have its merits (Andrei Alexandrescu's presentation on allocator is worth watching[2]), I think many still prefer explicit interfaces over implicit ones, so I don't see D take off in the near future, at least until the wanted features are added to the language.
[1] http://forum.dlang.org/thread/htmkdnmlqyvkidkrsmri@forum.dla...
Implicit numeric casts that lose bits are not allowed anymore.
> exception unsafety
??
That's good to hear, but I want implicit conversion to be forbidden unconditionally (even int * float -> float). I've been bitten by even widening conversions, so I just want to let them go away. It might be great if such an option could be applied to module level.
> ??
Sorry if my understandings or my words were wrong, but I got the impression that D also suffers from destructors that throw. (double-throw) Is this not the case in D? If so, could you elaborate how it achieves that?
I'm personally rather fond of purging exceptions from the language level completely, like Go did(Of course panics are technically exceptions, but their usage is culturally discouraged). Of course, this is just my personal opinion, but I would be happy if D had a story on this problem.
Not going to happen in a language with 11 integer types.
> (double-throw)
Exceptions are chained together.
There's the 'to' function to perform checked conversions and the standard cast operator to perform unchecked conversions.
int foo = 256; ubyte bar = foo.to!ubyte; // throws ConvException ubyte baz = cast(ubyte) foo; // overflows as expected
If I want more type-safety than this, I'll create simple wrappers:
struct Seconds { int value; alias value this; }
That way I can still pass seconds everywhere an int is expected, but I can now declare arguments to only accept Seconds and not ints.
Not that I'm a huge fan of exceptions either, but Go is definitely not a reference when it comes to error handling. My personal favorite, by far, is still CL's condition system.
Still, I've never had any issues with exceptions in D. Even exceptions thrown in destructors will raise a core.exception.FinalizeError with all the relevant information attached to it.
I've worked on large (1M+ LoCs) C++ codebases with exceptions disabled. Threading error codes all over the place, everywhere, is not pretty. Someone always forgets a check somewhere and you then spend 2 weeks figuring out why it crashes. It also pollutes the instruction stream with branches all over the place.
D also has assertions, unittests, preconditions, postconditions and invariants built into the language.
Also, unless there's actually an exception raised, frame handlers are faster than return code checks (its implemented as 3 MOV instructions per try block instead of test+jumps on every function call).
I don't think I'd want web and mail servers straight into the standard library out of the box. Those are way too specific. For example, there's already vibe.d for web servers and its doing an excellent job. They even have compile-time HTML templates!
As for IDEs, on Windows I've recently used VisualD and it did a good job. It's not yet as complete as the C# integration of Visual Studio (especially when taking ReSharper into account), but it definitely feels like an IDE. It already has improved hugely over when I last used it last year.
/bin/bash: line 1: 82501 Segmentation fault: 11 ./main
while I try to call a method on a null variable, which is not that friendly to newcomers.Sample code:
import std.stdio;
void main()
{
Greetings g = null;
g.hola();
} $ dmd -g main.d && ./main
Segmentation fault: 11
is there anything else I can try?I'm OSX El Capitan 10.11.1
import etc.linux.memoryerror;
shared static this() {
static if (is(typeof(registerMemoryErrorHandler)))
registerMemoryErrorHandler();
}to be honest, it's better than nothing but still kinda difficult to work with:
etc.linux.memoryerror.NullPointerError@src/etc/linux/memoryerror.d(325)
----------------
??:? void etc.linux.memoryerror.sigsegvUserspaceProcess(void*) [0x439045]
??:? void etc.linux.memoryerror.sigsegvDataHandler() [0x438f92]
??:? _Dmain [0x435c17]
??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [0x4376da]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x437630]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [0x437696]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x437630]
??:? _d_run_main [0x43758d]
??:? main [0x436075]
??:? __libc_start_main [0xb0fa1ec4]You can also do it manually btw with `addr2line -e your_executable 0x435c17` and the sort.
$ dmd -g main.d && ./main
etc.linux.memoryerror.NullPointerError@src/etc/linux/memoryerror.d(325)
----------------
??:? void etc.linux.memoryerror.sigsegvUserspaceProcess(void*) [0x423321]
??:? void etc.linux.memoryerror.sigsegvDataHandler() [0x42326e]
main.d:12 _Dmain [0x4204e5]
??:? _D2rt6dmain211_d_run_mainUiPPaPUAAaZiZ6runAllMFZ9__lambda1MFZv [0x421b8a]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x421ae0]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).runAll() [0x421b46]
??:? void rt.dmain2._d_run_main(int, char**, extern (C) int function(char[][])*).tryExec(scope void delegate()) [0x421ae0]
??:? _d_run_main [0x421a3d]
??:? main [0x420597]
??:? __libc_start_main [0xdb825ec4]
this is linux, I use mac as development machine, what can I do for mac? is there a error handler for mac too? $ cat main.d
import std.stdio;
class Greetings {
void hello() {
writeln("hello");
}
}
void main()
{
Greetings g = null;
g.hello();
}
$ dmd -g main.d && ./main
Segmentation fault: 11
$ uname -a
Darwin Johan-Ride-Mac.local 15.0.0 Darwin Kernel Version 15.0.0: Sat Sep 19 15:53:46 PDT 2015; root:xnu-3247.10.11~1/RELEASE_X86_64 x86_64
$ dmd --version
DMD64 D Compiler v2.069.0
Copyright (c) 1999-2015 by Digital Mars written by Walter Bright
(EDIT: added osx and dmd versions)I know it's because I'm trying to call a method on "null", but If I'm working on a huge code base, how do I debug this kind of issues if I don't have stack trace with line and error number of the error?
I'd like to get at least the stack trace and the error number with the line of the source code that caused the problem, like in golang:
$ cat main.go
package main
type Greetings struct {
}
func (g Greetings) hello() {
}
func main() {
g := &Greetings{} // heap instance
g = nil // intentionally shooting myself in the foot like I did in D
g.hello()
}
$ go run main.go
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x2025]
goroutine 1 [running]:
main.main()
/Users/ride/Documents/main.go:13 +0x15
goroutine 2 [runnable]:
runtime.forcegchelper()
/opt/boxen/homebrew/Cellar/go/1.4.2/libexec/src/runtime/proc.go:90
runtime.goexit()
/opt/boxen/homebrew/Cellar/go/1.4.2/libexec/src/runtime/asm_amd64.s:2232 +0x1
exit status 2If you are working in a big codebase and have this come up, you can enable core dumps and run it in a debugger to get far more information than just a line number (and line numbers are there too at least if compiled in debug mode).
2) Spanish ?
~/temp_code » ./greetings
Traceback (most recent call last)
greetings.nim(9) greetings
greetings.nim(6) hola
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
For the code: type
Greetings = ref object
name: string
proc hola(g: Greetings) =
echo g.name
var g: Greetings
g.hola()Only exception I promote is writing compilers in ML (esp Ocaml) because it's so good at doing that correctly. A concentration of compiler writers on such a great tool can only lead to an ecosystem whose quality C or C++ compilers will have trouble matching. Rewriting and testing the LLVM system at the least would be a good thing.
This is a quite important part of Rust (IMHO) because it gives the language a performance profile closer to C than C++ in many instances.
I think this is because Rust makes it far easier to use generics; they're type-safe and don't need to be pushed to header files. Also, the coding culture around Rust pushes one to use enums for dynamic dispatch instead of "trait objects" (Rust's equivalent of VFTs).
With the mythical smart enough compiler with god like devirtualizer it would be less of an issue but gratuitous use of virtuals annoys me no end. CRTP isn't that hard for the simple use cases but you are beholden to the god of compiler error messages when things go wrong. One great thing that clang has done (among others) is make g++ get their shit together. Contrary to the popular sentiment I personally find the current crop of g++ error messages more helpful.
Rust is marketed as a "general-purpose, multi-paradigm, compiled programming language" and D "originated as a re-engineering of C++", so may be I'm biased by my interpretation of the goals from both language designers.
In Rust you still can do "clever" things with generics to make them feel like C++, but the rest of the language is still closer to C: errors returned rather than thrown, no inheritance (but the "flat" OO and enums map well to what OO-like C programs do with "handles"), no constructors, etc.
WHATT??? did they do the same mistake as Go?
There's a number of things that make using this ergonomic: you can use the try! macro to convert it to a success value, returning an error value up the stack. You can use any of the combination methods on that page to chain various possibly-failing functions together into something that looks nice. Some people even use the word "monadic." We don't have if checks everywhere.
For more than you probably want to know on error handling in Rust, this guide just hit stable: https://doc.rust-lang.org/book/error-handling.html
Having errors on return has merits. Particularly it make error handling more deterministic. What it tends to do though, in languages like C and Go, is be very verbose.
Rust largely mitigates that through judicious usage of some of its higher levels features. Specifically its try! macro wrap up the common case of passing errors up the call stack quite neatly. With that and the pervasive RAII, Rust does a pretty good job of making error management quite unobtrusive.
It also has quite a nice way of converting errors types using traits that, in my opinion at least, handles typed error propagation better than most exception mechanisms I am familiar with.
From a personal pov, I like the determinism and I like the type conversion. I probably prefer D's scope mechanism to RAII in general as I like thinks to be as explicit as possibly but without being too verbose. But I think Rust seems to have hit quite a nice balance.
In any case, congratulations.
I wonder if the C++ to D translator could be used in other codebases, like Fltk for example.
I'm curious about this, although I don't think this is the first time I've heard D described as having a script-y feel to it. It sounds along the same lines as people describing Go as almost a scripting language, or comparing it to them. Neither Go nor D seem especially dynamic, both have compilers for reference implementations, both look more like C than Python. (To my eyes at least; I've never tried either one. Thus this question.)
Is it primarily the speed of the compilers? Both DMD and g6/g7/g8 boast very fast compiles, and Go uses somewhat script-y tools... but #!/usr/local/bin/tcc -run hasn't changed C's reputation. Fast compiling and memory safety, maybe?
Also check out https://github.com/Abscissa/scriptlike
That is to say: there have been technically backwards-incompatible changes, but every compiler and language, including those of C++ and Java, make technically backwards-incompatible changes in point releases (e.g. defining new functions in the libraries and modifying undefined behavior). Rust doesn't make changes that violate the stability guarantee. It goes further than that, in fact—any change that is not breaking according to the public guidelines but is thought possibly breaking in practice is tested against crates.io and feedback is solicited from those who have any private crates that might be affected to make sure it doesn't break them either.
(Unfortunately this policy is often misunderstood—for example, it's been claimed that Rust will make breaking changes as long as they don't break anything on crates.io, which is very much not an accurate description of it.)
Not to say that rust-the-language hasn't stabilized, but I think the point about D being (way) more mature still stands?
(Note, it's a good thing that people are writing code to test/work with/exercise latest rust/nightly etc -- but just because I can be fairly sure code I write today will compile tomorrow (which is good!) -- that doesn't mean "all of rust land" is stable).
Please don't take this of critique of all the hard work going into rust -- the only way this wouldn't be a thing was if rust core was a dead project.
[ed: To add to that - it might well be that all of the projects I've seen lately where I've had to wake up multirust[1] and do a little dance before my beautiful random code from random person on the Internet have been willing to compile -- have all been bad samples of rust code - but they're still part of the landscape of rust code one encounters in the wild.
[1] https://github.com/brson/multirust (eats a bit of space (~200MB per version of the compiler/toolchain) - but in my very limited testing works nicely!)]
In my experience most of these either use old features which have a stable counterpart, or use compile-time codegen (there are stable libraries to do this now, though they aren't as ergonomic as plugging into the compiler directly)
That said, I've mostly heard that lifetimes are still too young to be worth the trouble. So the one new thing Rust does bring to the table isn't really ready yet.
> lifetimes are still too young
While some stuff is new, the core of the idea goes back to Cyclone in 2001.Not in my experience. I wrote tons of safe Rust every day and I really enjoy not having to worry about difficult-to-debug crashes, undefined behavior, and security problems.
Most of these things are new to the systems space, though. Things like algebraic data types, etc. Also usable affine types.
> lifetimes are still too young to be worth the trouble.
I've heard pretty much the opposite. Firstly, nobody who uses Rust calls the feature "lifetimes" (it's usually called borrowing or borrow checking). That's the name of the syntax, and while the syntax is new and somewhat confusing it doesn't pop up that much thanks to elision.
Borrowing has a learning curve, yes. But really, it's an equivalent set of principles to what you would do in C++ to keep your pointers safe. The difference is that you can write small C++ codebases without worrying about "C++ borrowing", whereas you need to think about these things off the bat in Rust.
The things you can prove statically with the borrow checker are a subset of the things that wont trigger UB in C++.
My point was: If you're working on a large C++ codebase you will have to reason roughly about who owns what and where your pointery data is coming from. IME it amounts to (roughly) the same thing as Rust's borrowck rules, just that Rust forces you to think about this a priori, in a clean framework with clear rules, instead of wibbly wobbly, pointy-wointy, ... stuff.
The things you feel safe to do in Rust are often a superset of the things you feel safe to do in C++. Most large codebases often use lots of shared_ptr or equivalent, whereas in Rust Rc and RefCell are broken out only when necessary (when the compiler tells us there's no way to do it the borrow way).
So while C++ might let you write a larger variety of non-UB patterns in principle, Rust will let you write more non-UB patterns in practice. You feel safe playing fast and loose with the references, and dancing near the line of safe behavior since the compiler ensures you never cross it. I have a post illustrating an example of this here: http://manishearth.github.io/blog/2015/05/03/where-rust-real...
And, of course, if you really want to express one of those "things C++ lets you do" wrt pointers, you can always break out `unsafe`.
Most large C++ projects do not unnecessarily use reference counting.
Most C++ programmers do not fear using references or pointers because of invalidation.
The Rust compiler does not tell you when it's impossible to satisfy your problem using borrows. In fact it often has false positives requiring borrow gymnastics because it is too primitive.
And your last line is a complete reversal of your previous attitude: Suddenly you are focusing on theory while ignoring that in practice you can not usually break out `unsafe` to solve your borrow problems. In fact it's Rust, not C++, that is more likely to reach for more heavyweight abstractions than necessary because it's not practical to litter unsafe throughout your codebase.
Compared to Rust? I doubt that. Like I said, Rust lets you dance near the line.
This is from experience in various large C++ codebases. I'm not saying people use refcounting a lot, I'm saying it gets used more than Rust.
YMMV though, so it does boil down to a matter of different experiences here. We'll probably have to agree to disagree.
> In fact it often has false positives requiring borrow gymnastics because it is too primitive.
Not really. Aside from non-lexical borrows and a couple other nice-to-have things (but not necessary), the borrow checker is pretty precise for what it tries to prove.
One might argue that the guarantees Rust tries to maintain (one writer or multiple readers for a piece of data) are too primitive. I don't think that's true. Doing a context-sensitive/flow-sensitive analysis might lead to more patterns being allowed but it's hard to scope guarantees when the analysis is interprocedural -- stopping at function boundaries makes sense to me.
> The Rust compiler does not tell you when it's impossible to satisfy your problem using borrows.
It sort of does. If you try to introduce borrows and listen to the suggestions the compiler gives you, and eventually end up nowhere, it's probably not possible to do it that way. It's not perfect, but it's good enough. And it's immune to further changes -- you don't need to design your pointer usage so that it's future-proof; design it however you want, and if a future refactoring introduces a possible use-after-free, fix the compile error.
> And your last line is a complete reversal of your previous attitude: Suddenly you are focusing on theory while ignoring that in practice you can not usually break out `unsafe` to solve your borrow problems.
No, it's not, I'm just pointing out the equivalence. My point was that "The things you can prove statically with the borrow checker are a subset of the things that wont trigger UB in C++." is irrelevant for two reasons -- (a) in practice IMO the things Rust makes you feel safe to do is a superset of what C++ lets you feel safe to do, and (b) if we're going to talk about "all possible things C++ theoretically allows you to do", then you should include unsafe -- you were comparing "safe Rust" with "all C++", which is unfair here, since the entities to be compared for what you're theoretically allowed to do are "all Rust" and "all C++". I focused on a different flaw in your argument, so of course the focus changed. I'm not saying you should use unsafe a lot, I'm saying "what C++ lets you do is equivalent to using unsafe a bit more often in Rust". I don't endorse it, but if you consider "C++ lets you do so many things Rust doesn't" to be a plus point of C++ (I don't), then you should at least be comparing the right things and allowing yourself to use unsafe in Rust too.
> more heavyweight abstractions than necessary
Examples?
Your problem is that you are trying to imagine what happens in the real world rather than just looking at it.
> This is from experience in various large C++ codebases. I'm not saying people use refcounting a lot, I'm saying it gets used more than Rust.
I count 56 imports of Arc and 37 of Rc in servo, would you wager the equivalent parts of chromium or firefox use more ref-counting?
> Not really. Aside from non-lexical borrows and a couple other nice-to-have things (but not necessary), the borrow checker is pretty precise for what it tries to prove.
The problem is that the borrow checker does not look at the function as a whole. You are a slave to the borrow checker, you can only consider other things when it has been satisfied, even if its trivially safe from a whole-function view. Some simple things aren't even expressible at all without non-lexical borrows, no matter what gymnastics you try.
> It sort of does. If you try to introduce borrows and listen to the suggestions the compiler gives you, and eventually end up nowhere, it's probably not possible to do it that way.
That sounds like a terrible workflow that I don't think Rust programmers use in the real world. I think they usually use shared ownership because they know they have shared ownership, not because they gave up on the borrow checker. Any instance of actually resorting to reference counting because of the inability to make the borrow checker happy actually counts against Rust, not for it.
> in practice IMO the things Rust makes you feel safe to do is a superset of what C++ lets you feel safe to do
People routinely do safe things in C++ that would be impossible to express in safe Rust code. You need to let go if this untruth.
> if we're going to talk about "all possible things C++ theoretically allows you to do", then you should include unsafe -- you were comparing "safe Rust" with "all C++", which is unfair here, since the entities to be compared for what you're theoretically allowed to do are "all Rust" and "all C++".
I focused on safe Rust because your argument was that safe Rust is just as powerful as C++, just proved safe statically. This is far from the truth and you refuse to accept the correction. Most people in the Rust community would probably say they accept the loss in power because they prefer absolute safety. This is a very defensive position. It's impressive just how little power Rust gives up in comparison to languages like C#/Java. But to pretend that you actually have more power in Rust due to psychological effects is just zealotry and doesn't reflect how people use C++ in the real world.
> Examples?
Any abstraction that uses dynamic checking that would be considered a bug to actually trigger. Some examples include borrowing a RefCell, random access of iterators, sequential access of certain kinds of iterator adaptors, etc.
No, it wasn't; sorry if you thought it was. My argument was that the cognitive overhead of satisfying the borrow checker isn't much different from the cognitive overhead in writing safe C++ code. Perhaps I shouldn't have used the word "equivalent", but that was my original point.
Then you said "The things you can prove statically with the borrow checker are a subset of the things that wont trigger UB in C++.", and I made the theory/in practice distinction, but at no point did I say that was wrong, I just said it wasn't relevant to my point.
Just to be clear, this is the set of things I believe:
- Rust lifetimes/borrowchk have little to no additional cognitive overhead as compared to writing safe code in C++
- Rust lets you approach problems without having to worry so much about safety, which lets you play more fast and loose with references (avoiding unnecessary refcounting, etc)
I agree with you that:
- C++ lets you do more patterns safely than safe Rust lets you compile; and some of these patterns are used often.
- Rust needs to work on improving on things like nonlexical borrows.
I disagree that the above two are major problems or come up often in practice whilst programming Rust. It could be that we've had different experiences whilst programming Rust, however.
> I count 56 imports of Arc and 37 of Rc in servo, would you wager the equivalent parts of chromium or firefox use more ref-counting?
Sure. Firefox has two garbage collectors. Not one. Two. (One of them is Spidermonkey's, which is expected, since Javascript is GCd. The other is for the DOM)
Firefox also has many different types of FooRefCounted base classes, which seem to be used all over the place.
Almost all the Arc is in the highly-parallel layout.
Note that "imports Rc" just means that it deals with an Rc'd value, not necessarily introduces a new Rc'd value. (On the other hand in C++ often you use base classes for Rc, which means that the derived class is Rcd everywhere)
For example, in all of the Servo DOM code, the Page is Rc'd (I forgot the reason, but we don't thrash the refcount too much there), and JS callbacks are Rcd (because of a sticky interaction with the JS runtime, to be expected of something like that), and nothing else. That itself accounts for 20 imports of Rc, even though it's just two kinds of things being Rcd. Arcs are used in code interfacing with the network or layout stack (I think, I haven't looked closely)
> People routinely do safe things in C++ that would be impossible to express in safe Rust code. You need to let go if this untruth.
I agree with this. That's not what I'm saying. I'm not refuting the existence of safe things C++ lets you do that Rust doesn't. I'm just saying that Rust lets you feel safer
> I don't think Rust programmers use in the real world.
Sure, that's not how we program. Because for the vast majority of cases since lifetime annotations exist it's pretty easy (for a Rust programmer) to figure out how to borrow things safely. But there are cases where you're unsure; and it takes very little time to try something out and see what happens.
> Any instance of actually resorting to reference counting because of the inability to make the borrow checker happy actually counts against Rust, not for it.
I don't see refcounting being used to "make the borrow checker happy"; I see it being used in cases where its necessary.
> Some simple things aren't even expressible at all without non-lexical borrows, no matter what gymnastics you try.
I disagree, in my experience it's very rare to hit a situation where you need nonlexical borrows.
> Some examples include borrowing a RefCell
Ah, right. I don't consider refcell too "heavyweight" (and often it can be replaced with the zero-cost Cell), but yes, it does get broken out more than necessary sometimes. I agree with you on that.
Note that the a hype train is a two-edged sword, it will both bring a strong community and might also induce a cliff with the other communities (praising a single god is never a good thing).
1: I've used lower level languages, but for nothing large, and I haven't started a project in C, C++ or even Java in over a decade, and was never very enamored.
Oh, and I'm not sure "majority" is really true. Yeah, maybe some of us have disproportionate impact, but a very large number of people in Rust-land really know their systems stuff. I am humbled to work alongside such talented people.
That said, D's compatibility with C++ is a big plus.
Well, not really. Higher-kinded type parameters are something that requires a kind (typeclass) system in the first place, which D intentionally doesn't have (as D's authors are opposed in principle).
I'm well aware that the formal definition of an HKT is just a type with a higher kind, and that's irrelevant to this discussion.
Saying C++ has HKT because it doesn't have typeclasses is like saying Python has all of Haskell's type system features because it doesn't have static types. There's a sort of vacuous sense in which it's true, but it's not a particularly meaningful or interesting thing to say.
> Saying C++ has HKT because it doesn't have typeclasses
I didn't say anything like that at all. C++ has HKT, and it doesn't have type classes.
Patrick is talking about HKTs which can be typechecked at the source, similar to the rest of Rust generics, when he talks about "they want to create typeclasses that abstract over types of a higher kind and only those types". C++ templates get typechecked at use-time, not instantiation-time, like Rust macros. On the other hand, Rust forces you to constrain types in generics (a la C++ "concepts") so that it typechecks at the source (and then the contract for types being passed into the function is clear in the signature). We don't have higher kinded bounds, though.
HKT, after all, is a comment on bounds. What sort of things are you allowed to substitute for this generic or template parameter?
What seems to be happening here is a difference of opinion on what constitutes a "bound", in the C++ world a "structural" bound (i.e. type vs number vs template) is all you have. In the Rust world this isn't considered a bound, since the bound isn't type-safe. In the Rust world, Haskell world, and most other worlds where the word "kind" is used, a bound is a "type" (typeclass) bound and is type safe -- you can only do operations on the parameter which the bounds let you. Many Rust/Haskell people would consider C++ templates to be a particularly awesome macro system (but not a generics system); since they're structural-bound (like Rust macros); not type-safe.
Really a matter of terminology though.
However you can write gc free code regions by marking them as @nogc and the compiler will make sure you only call code that is @nogc compatible.
Additionally, there is some ongoing discussion how to improve the language to depend less on the GC.
The C++ core guidelines are great, and me being a C++ fan even with its warts, welcome them and all the work the C++ community is doing.
However them being opt-in, relying on static analysis and the quality of the code I usually see at companies, which tends to be C with Classes from developers that don't even know what CppCon is, I am not sure how the adoption will be like.