logoalt Hacker News

safercplusplustoday at 12:50 AM1 replyview on HN

> From what I'm aware of, Rust has poor ergonomics for programs that have non-hierarchical ownership model (ie. not representable by trees)

Yeah, non-hierarchical references don't really lend themselves to static safety enforcement, so the question is what kind of run-time support the language has for non-hierarchical references. But here Rust has a disadvantage in that its moves are (necessarily) trivial and destructive.

For example, the scpptool-enforced memory-safe subset of C++ has non-owning smart pointers that safely support non-hierarchical (and even cyclical) referencing.

They work by wrapping the target object's type in a transparent wrapper that adds a destructor that informs any targeting smart pointers that the object is about to become invalid (or, optionally, any other action that can ensure memory safety). (You can avoid needing to wrap the target object's type by using a "proxy" object.)

Since they're non-owning, these smart pointers don't impose any restrictions on when/where/how they, or their target objects, are allocated, and can be used more-or-less as drop-in replacements for raw pointers.

Unfortunately, this technique can't be duplicated in Rust. One reason being that in Rust, if an object is moved, its original memory location becomes invalid without any destructor/drop function being called. So there's no opportunity to inform any targeting (smart) pointers of the invalidation. So, as you noted, the options in Rust are less optimal. (Not just "ergonomically", but in terms of performance, memory efficiency, and/or correctness checking.) And they're intrusive. They require that the target objects be allocated in certain ways.

Rust's policy of moves being (necessarily) trivial and destructive has some advantages, but it is not required (or arguably even helpful) for achieving "minimal-overhead" memory safety. And it comes with this significant cost in terms of non-hierarchical references.

So it seems to me that, at least in theory, an enforced memory-safe subset of C++, that does not add any requirements regarding moves being trivial or destructive, would be a more natural progression from traditional C++.


Replies

Animatstoday at 5:53 AM

> Yeah, non-hierarchical references don't really lend themselves to static safety enforcement, so the question is what kind of run-time support the language has for non-hierarchical references.

Yes. Back references are a big problem.

I just wrote a bidirectional transitive closure algorithm that uses many back references, with heavy use of Rc, RefCell, Weak, and ".borrow()". It's 100% safe rust. This is the "proper" Rust way to write this sort of thing. The nice thing about doing it the "right" way was that, once it compiled, it needed few changes to work correctly. No mysterious errors at all. But it took a lot of work to get it to compile. Some sections had to be rewritten to get the ownership plumbing right.

I put it up on the Rust forums for comments, and got replies that I should stop doing all that fancy stuff and just use indices into arrays.[1] Or arena allocation. Things that bypass the Rust ownership system. Those approaches would probably have more bugs.

(I'm starting to see a way to do compile time checking for this sort of thing. The basic concept is that run time borrows must be disjoint as to type, disjoint as to scope, or disjoint as to instance. The first is easy. The second requires inspecting the call chain, and there are problems with templates due to ambiguity over what a type parameter does in .borrow() activity. The third is almost a theorem proving problem, but if you restrict compile time checks for disjoint instances to a single function (or maybe a "class", a struct and its functions), it might be manageable. All this might take too much cleverness to use in practice. Too much time getting the ownership plumbing right, even with compiler support. But I should write this up.)

[1] https://users.rust-lang.org/t/bidrectional-transitive-closur...

show 1 reply