> The argument here seems to be that there is can be no real abstraction in low-level languages, so there's no point providing language features for abstraction.
My argument is that low-level languages allow for low abstraction, i.e. there's little that they can abstract over, where by abstraction I mean hide internal implementation details in a way that when they change the consumer of the construct, or "abstraction", does not need to change; if it does, then the construct is not an abstraction. With "zero-cost abstraction," C++/Rust offer constructs that syntactically appear as if they were abstractions (e.g. static vs dynamic dispatch; subroutine vs. coroutine call), but in reality aren't. I am not aware of any other language (unless Ada has changed considerably since I last used it in the early '00s) that values this idea to such a great extent.
The things you mentioned are abstractions only in the sense that the user doesn't need to know how the compiler implements them; in that respect, every language construct, including `if` (e.g. in Java, not every if is compiled into a branch) is an abstraction. I speak of the language's ability to allow users to abstract, and in that regard all low-level languages provide for poor abstraction. Without a JIT, the caller needs to know the calling convention; without a tracing GC the caller needs to know how the memory pointed to by a returned value is to be deallocated. The question is how much you try to make sure that all this knowledge is implicit in the syntax.
> People often argue that Rust is too complicated for its goal of memory safety.
I didn't know people often say that. I said it, and I'm not at all sure that's the case. I think that Rust pays far too heavy a price in complexity. It's too heavy for my taste whether or not it's all necessary for sound memory safety, but if it isn't, all the more the shame.
> Most of the features that look like they're there solely to support "zero-cost abstractions"—traits, for example—are really needed to achieve memory safety too.
OK, so I'll take your word for it and not say that again.
> The most important static analyzers used in industry today are Clang's sanitizers
I'm talking about sound static analysis tools, like Trust-in-Soft, that can guarantee no UB in C code. I think that particular tool might support some subset of C++, but not all of it. The sanitizers you mention rely on concrete interpretation (aka "dynamic") and are, therefore, usually unsound. Sound static analysis requires abstract interpretation, of which type checking and type inference are special cases. Just as you can't make all of Rust's guarantees by running Rust's type-checker on LLVM bytecode, so too you cannot run today's most powerful sound static analysis tools -- that are already strong enough to absolutely guarantee no UB in C st little cost -- on LLVM bytecode; they require a higher-level language. Don't know about tomorrow's tools.
> Again: easy to say, harder to specify specific Rust features you think should be removed.
I accept your claim. In general, I don't like to isolate language features; it's the gestalt that matters, and it's possible that once Rust committed to sound memory safety everything else followed. But let me just ask: are macros absolutely essential?
> But let me just ask: are macros absolutely essential?
Yes, for two main reasons: (1) type-safe printf; (2) #[derive]. Nothing is "absolutely essential" in any Turing-complete language, of course, but printf comes pretty close. The only real alternative would have been to use the builder pattern for formatting like C++ does (i.e. the << operator), but nobody seriously proposed that as the aesthetics are really bad and setting formatting flags like precision is problematic. And without custom #[derive], you couldn't serialize types, which is pretty important in a modern language.
Note that these are both cases (maybe the primary cases) in which OCaml has built-in ad-hoc solutions that feel very un-"systemsy". In OCaml, format strings are built in to the language, as are functions like PartialEq and Debug, the latter of which are implemented via magic built-in functions that call private internal reflection APIs. There was a desire to do better in Rust, as it was felt that these are the ugliest parts of OCaml, and so macros were part of Rust from the very early days.
OK. I mean, personally I would swallow a lot of ugly special cases before adding macros, but it's certainly in line with other people's aesthetic preferences and Rust's "C++ spirit" of a low-level language with high-level language features. I can see the logic in saying that since we need lots of fancy mechanisms for sound safety anyway, what's one more gonna hurt?
BTW, Zig manages to do both things without macros and without any special cases in the compiler. TBF, Zig's approach was unfamiliar to me until I saw it in Zig, and it is Zig's "brilliant idea" (even if it had originated elsewhere), so it's OK if Rust simply didn't consider it; Rust certainly has its own brilliant idea.
My argument is that low-level languages allow for low abstraction, i.e. there's little that they can abstract over, where by abstraction I mean hide internal implementation details in a way that when they change the consumer of the construct, or "abstraction", does not need to change; if it does, then the construct is not an abstraction. With "zero-cost abstraction," C++/Rust offer constructs that syntactically appear as if they were abstractions (e.g. static vs dynamic dispatch; subroutine vs. coroutine call), but in reality aren't. I am not aware of any other language (unless Ada has changed considerably since I last used it in the early '00s) that values this idea to such a great extent.
The things you mentioned are abstractions only in the sense that the user doesn't need to know how the compiler implements them; in that respect, every language construct, including `if` (e.g. in Java, not every if is compiled into a branch) is an abstraction. I speak of the language's ability to allow users to abstract, and in that regard all low-level languages provide for poor abstraction. Without a JIT, the caller needs to know the calling convention; without a tracing GC the caller needs to know how the memory pointed to by a returned value is to be deallocated. The question is how much you try to make sure that all this knowledge is implicit in the syntax.
> People often argue that Rust is too complicated for its goal of memory safety.
I didn't know people often say that. I said it, and I'm not at all sure that's the case. I think that Rust pays far too heavy a price in complexity. It's too heavy for my taste whether or not it's all necessary for sound memory safety, but if it isn't, all the more the shame.
> Most of the features that look like they're there solely to support "zero-cost abstractions"—traits, for example—are really needed to achieve memory safety too.
OK, so I'll take your word for it and not say that again.
> The most important static analyzers used in industry today are Clang's sanitizers
I'm talking about sound static analysis tools, like Trust-in-Soft, that can guarantee no UB in C code. I think that particular tool might support some subset of C++, but not all of it. The sanitizers you mention rely on concrete interpretation (aka "dynamic") and are, therefore, usually unsound. Sound static analysis requires abstract interpretation, of which type checking and type inference are special cases. Just as you can't make all of Rust's guarantees by running Rust's type-checker on LLVM bytecode, so too you cannot run today's most powerful sound static analysis tools -- that are already strong enough to absolutely guarantee no UB in C st little cost -- on LLVM bytecode; they require a higher-level language. Don't know about tomorrow's tools.
> Again: easy to say, harder to specify specific Rust features you think should be removed.
I accept your claim. In general, I don't like to isolate language features; it's the gestalt that matters, and it's possible that once Rust committed to sound memory safety everything else followed. But let me just ask: are macros absolutely essential?