> I sometimes feel C and C++ were very clear on where data lives (stack vs heap) and how it’s organized (struct alignment), while Rust seems a little more opaque . I’ve felt a similar way working in Go.
This is interesting to me. I fully agree about this with Go (and in the past I've sometimes seen this make optimizations difficult as in practice it's hard to keep track of heap allocations other than runtime inspection), but I feel like Rust is actually better at C++ than this. Alignment is certainly a different beast, as by default I don't think you can really assume anything about how Rust will lay out a struct (with the workarounds being various `repr` attributes), but in terms of heap allocations, I'd argue there isn't anything as ambiguous as a raw C++ pointer. If you're able to get away with smart pointers all of the time, I could see this being less of an issue, but from my somewhat limited experience with C++ there seem to often be cases where APIs still expect raw pointers from time to time, so I wouldn't expect to be able to look at some random function call in a call graph and know what type of memory it's dealing with in the absence of documentation or runtime inspection.
In Rust, it's a `Box<T>`, `Rc<T>`, `Arc<T>`, `Vec<T>`, or `String`, it's on the heap. If it's not, chances are it's on the stack. There are separate types for the non-owning versions of those types for references (`&T`), slices (`&[T]`), and string references (`&str)`, none of which require heap allocations to create (although they might indirectly refer to heap-allocated data in one of the other types mentioned before). There are probably other types that one might run into that are heap-allocated, but even when dealing with something like indirection from dynamic dispatch, any heap allocations needed to make things work will end up being explicit via something like `Box` or `Arc`. I might just be misunderstanding the point being made here; maybe the author was looking for documentation rather than relying on the types themselves, or maybe they had reason to be concerned about whether the type behind a reference or slice happened to be heap allocated or not, but in my experience, only needing to care about that in the context of when explicitly making a new allocation is a benefit, not a drawback.
In C++, in particular when restricting to a C like subset, I prefer looking at an expression like
foo->bar.baz
instead of (in Rust and other modern languages that decided to get rid of the distinction)
foo.bar.baz
For example, the former lets me easily see that I can copy foo->bar and I now have a copy of baz (and indeed bar). In a newer language, it's harder to see whether we are copying a value or a reference.
I see what you're saying but I'd argue that this is mostly an unnecessary thing to worry about because with the exception of types explicitly opted into being cheaply copyable, you're going to be moving it if you're not accessing it via a reference. The idea is that if you're worried about expensive copies, it shouldn't be possible to copy implicitly in the first place; you'd either explicitly `clone` or you wouldn't be copying it at all.
This is interesting to me. I fully agree about this with Go (and in the past I've sometimes seen this make optimizations difficult as in practice it's hard to keep track of heap allocations other than runtime inspection), but I feel like Rust is actually better at C++ than this. Alignment is certainly a different beast, as by default I don't think you can really assume anything about how Rust will lay out a struct (with the workarounds being various `repr` attributes), but in terms of heap allocations, I'd argue there isn't anything as ambiguous as a raw C++ pointer. If you're able to get away with smart pointers all of the time, I could see this being less of an issue, but from my somewhat limited experience with C++ there seem to often be cases where APIs still expect raw pointers from time to time, so I wouldn't expect to be able to look at some random function call in a call graph and know what type of memory it's dealing with in the absence of documentation or runtime inspection.
In Rust, it's a `Box<T>`, `Rc<T>`, `Arc<T>`, `Vec<T>`, or `String`, it's on the heap. If it's not, chances are it's on the stack. There are separate types for the non-owning versions of those types for references (`&T`), slices (`&[T]`), and string references (`&str)`, none of which require heap allocations to create (although they might indirectly refer to heap-allocated data in one of the other types mentioned before). There are probably other types that one might run into that are heap-allocated, but even when dealing with something like indirection from dynamic dispatch, any heap allocations needed to make things work will end up being explicit via something like `Box` or `Arc`. I might just be misunderstanding the point being made here; maybe the author was looking for documentation rather than relying on the types themselves, or maybe they had reason to be concerned about whether the type behind a reference or slice happened to be heap allocated or not, but in my experience, only needing to care about that in the context of when explicitly making a new allocation is a benefit, not a drawback.
Would love built in coverage support but investigation is needed on the design (https://github.com/rust-lang/cargo/issues/13040) and we likely need to redo how we handle doctests (https://blog.rust-lang.org/inside-rust/2025/10/01/this-devel...).