Other people have said this, but as a concrete example of the difference between #[no_mangle] and extern: Rust does a thing called return value optimization (RVO), C does not. If you have the following library header:
typedef struct { int x[500]; } bigthing;
bigthing MyFunction();
and a program using that library:
int main() {
bigthing x = MyFunction();
}
C has no choice but to have MyFunction allocate a couple kilobytes on the stack and have main copy the structure from the stack to where it should eventually go. The equivalent Rust code, however, can pass the address of the object x to MyFunction, as if it were actually void MyFunction(bigstruct &output).
If you have a #[no_mangle] but not extern function in Rust, the Rust compiler will generate code for that function that looks for this secret by-reference argument and fills it in. When you call it from C (or something that calls functions in a C-like manner, like Python's cffi), it won't be setting up the call like that at all, and it will expect to read the result off the stack like a C function would have done.
(I don't think there's a lot of use for #[no_mangle] without extern. The best I can think of is that, if you have a Rust application that dynamically loads a Rust library and runs a function from it, you don't have access to the mangling algorithm, once you find the function you'll call it according to the Rust ABI. But even that is risky since the Rust ABI can change between compiler versions; you're still better off shoveling things through the C ABI.)
> C has no choice but to have MyFunction allocate a couple kilobytes on the stack and have main copy the structure from the stack to where it should eventually go. The equivalent Rust code, however, can pass the address of the object x to MyFunction, as if it were actually void MyFunction(bigstruct &output).
What sort of C implementation (ABI, compiler, etc.) are you thinking of here? gcc (x86, x86-64, ARM) is perfectly capable of doing the exact optimization that you describe Rust being able to do.
Across a public interface in a shared library? I know it can do that optimization within an object, but the SysV ABI does not (to my knowledge) let it expose that optimization at a shared library boundary. I believe Rust has that optimization as part of its inter-library ABI (partly because Rust's ABI is not stable).
I guess the trick here is that "C" really means "platform ABI" and isn't inherently about a language or a compiler.
Yes, across a public interface in a shared library. The SysV ABI just requires that the callee passes a pointer to the structure return value as a hidden parameter; there's nothing saying that pointer must point to different memory than the named object in the program. (Consider how you would implement the call to MyFunction and MyFunction itself at an assembly level that would absolutely require the compiler to allocate two different bigthing objects.)
If you have a #[no_mangle] but not extern function in Rust, the Rust compiler will generate code for that function that looks for this secret by-reference argument and fills it in. When you call it from C (or something that calls functions in a C-like manner, like Python's cffi), it won't be setting up the call like that at all, and it will expect to read the result off the stack like a C function would have done.
(I don't think there's a lot of use for #[no_mangle] without extern. The best I can think of is that, if you have a Rust application that dynamically loads a Rust library and runs a function from it, you don't have access to the mangling algorithm, once you find the function you'll call it according to the Rust ABI. But even that is risky since the Rust ABI can change between compiler versions; you're still better off shoveling things through the C ABI.)