#include <iostream>
void p(const std::string &x) {
std::cout << x << std::endl;
}
int main() {
p("hello");
}
Neither p, which takes an already-allocated std::string, nor main, which uses a string literal, explicitly/obviously requests dynamic memory allocation. Yet it happens.
In userspace, that's not a huge problem - even if allocation requires asking for more memory from the kernel, which requires paging out some dirty files to disk, which blocks for a few seconds on NFS, that's fine - the kernel will just suspend the userspace process for a few seconds while all that happens. Inside the pager or NFS implementation, though, an unintentional memory allocation can deadlock the whole system.
In Rust you'd have to write .to_owned() to do the equivalent, and it would be obvious that doing it in code that might be responsible for freeing up memory is a mistake.
Something as innocuous as `T x;` or `T x = y;` will allocate in C++. In Rust, the equivalents are `let x = T::new();` and `let x = y.clone();` which are much more obvious as potential allocations.
> Something as innocuous as `T x;` or `T x = y;` will allocate in C++
I think saying these will allocate is wrong. They can allocate, but only if T allocates and manages memory, which depends on what T does.
I am skeptical of the more general claim. Anything but a truly cursory skim of the code is going to tell you that these lines are constructing a new object, regardless of the syntax, and to actually know whether it allocates memory, you need to know more about T, which is true in either Rust or C++.
Unless someone implements Copy on a type that allocates in Rust (which you shouldn't do; Copy is specifically for types that are cheap to copy), you really won't get implicit copies, though.
That means in any reasonable code, `let x = y` won't allocate in Rust while `T x = y` could in C++. `f(x)` in Rust won't either for the same reason. And that's on top of how C++ will implicitly take a reference, so even if you know x is a heavy object, you don't know if `f(x)` will be expensive (or if `f(move(x))` would be helpful).
It's just not exactly equivalent. C++ is more implicit and has less baked-in good practices like `Copy` vs `Clone`. The indirection is often shallower (less often I have to inspect a function prototype or type I'm using for specifics), and I find that very useful.
Minor clarification: afaik it's impossible to implement Copy such that it would allocate. Unlike Clone, the Copy trait is only a marker trait which doesn't have methods, so you cannot customize its behavior. It will always be a bitwise stack copy.
This is not minor. In Rust "x = y" specifically only copies or moves bytes, no magic involved, no code is run except something like memcpy.
- If the type isn't marked Copy, it's a bytes move (which will very probably be optimized out).
- If the type is marked Copy (which is possible only if all its subtypes are also marked Copy, which allocating types are not) then it's a bytes-for-bytes copy.
As soon as you need something more involved, then you have to implement (or derive for simple types) Clone.