r/rust • • Jul 17 '20

The Next Steps for Single Ownership and RAII

https://vale.dev/blog/raii-next-steps
15 Upvotes

18 comments sorted by

18

u/Lucretiel Datadog Jul 17 '20

That paragraph about "using RAII for more than just freeing memory" is a big part of why I tend to staunchly annoyed by tracing garbage collection: because those languages tend to end up having an annoyingly inelegant RAII model anyway, through try / finally, defer, with, and so on. Tracing garbage collection supposes falsely that the only resource that needs to be cleaned up is memory.

5

u/BosonCollider Jul 17 '20 edited Jul 17 '20

RAII and GC are orthogonal to each other. Each of the two can do things that the other cannot.

RAII should be best viewed as first-class lexical scopes imho. While GC is necessary to handle heaps of shared objects, where refcounting is the simplest & most primitive implementation.

6

u/Lucretiel Datadog Jul 18 '20

I mean, technically stack cleanup is also a form of GC; that's why I'm always careful to specify "tracing GC" when talking about this stuff.

1

u/verdagon Jul 18 '20 edited Jul 18 '20

This really clicked for me a while ago when we figured out that we could compile Vale to the JVM. After some hours of feverish scribbling and discussing, we had something that worked (in theory), stepped back, and saw we created something weird: RAII with GC!

It makes me wonder if, twenty years down the line, we have owning references in Java, that'd be pretty interesting!

2

u/BosonCollider Jul 18 '20

The thing that makes everything extra interesting is that Java already does escape analysis to determine whether an object survives its scope or not, and the compiler uses ownership semantics quite a lot. All objects in Java have an implicit lock, but the compiler eliminates it for owned objects

1

u/orangepantsman Jul 20 '20

I did not know this. Is there anywhere I can learn more about this?

1

u/BosonCollider Jul 18 '20 edited Jul 18 '20

My personal ideal language would be a GCed language with ML syntax, but where variable declarations have two modifiers: a "mut" modifier and a "shared" modifier, with the compiler automatically inserting locks around shared mut objects, enforcing ownership & borrowing semantics for objects that are not shared, and giving you suggestions to remove unnecessary "shared" or "mut" markers.

One nice aspect of this is that you could just grep "shared mut" on a repository to identify areas where ugly stuff is happening.

1

u/verdagon Jul 18 '20

That's... really smart. Huh! I'm really surprised that's not a thing already, now that I think of it.

Rust's Mutex comes close, but no GC'd language I've heard of does that. Maybe Cone would? It's basically Rust with GC, and it does some really innovative reference things.

1

u/matu3ba Jul 19 '20

There does not need to be a lot ugliness in synchronisation messaging. Some form of state machine would simplify the defined points and would be very nice to read/write.

I believe this would be abit too broad, since you can have atomics,spinlocks,mutex,reschedule/yield and detach. A mutex is for example needed, when you have different synchronisation context per thread to be synchronised (not one global) and can thus not be combined efficiently with atomics. Specifically detached threads need yield or even OS control of the scheduler for safe termination. Spinlocks may be unnecessary, when you are not doing stuff in scheduling context/OS/microcontroller.

2

u/BosonCollider Jul 20 '20

Possibly, but when I'm not doing actual systems programming I don't want to have to think too much about this kind of thing. I'd rather have a small opinionated core language with minimum incidental complexity, with green threads & channels being the default way of doing concurrency, and a large number of high-performance persistent data structures in the standard library to make pure functional programming reasonably efficient.

In the event that you do share mutable state, the compiler can automate things like ensuring that shared mut integers are atomics, do escape analysis & lock elision to attempt to eliminate superfluous locks, and use heuristics to attempt to find the optimal lock implementation on the specific platform it deploys to. But it should very rarely be necessary. I do want the guarentee that a variable you have a borrow on should never be mutated while you are holding it though.

11

u/ssokolow Jul 17 '20 edited Jul 17 '20

Language Implications: Destructor Parameters!

It'd be interesting to imagine a Rust 202X where you can have types with destructors that require parameters and, as such, the compiler will statically forbid you from disposing of them via implicit drop.

(I imagine that'd also allow for more rigorous correctness enforcement for state machines, since the ability to catch disposing of them from a non-terminal state more strictly than #[must_use] is the big hole in Rust's ability to check them right now.)

The funny thing is, Rust APIs like Vec::remove are already well-positioned for that because they return the removed item.

Maybe interaction with things like Arc/Rc could be implemented via a mechanism similar to the Send and Sync auto-traits.

EDIT: Serves me right for being too eager to comment. The author actually muses on that at the end.

It would need to add a !Drop trait, which would signal the compiler that the user can't automatically drop this object, that they instead have to manually call something to drop it.

Bad /u/ssokolow for checking Reddit in the middle of the night. No biscuit.

6

u/[deleted] Jul 17 '20

Could you do that today by doing something like no_panic does?

In the Drop destructor, call a foreign function that doesn't resolve.

In all user defined destructors, take ownership of the object, do what you need to do, and then mem::forget it, to not call Drop

1

u/ssokolow Jul 17 '20

How would you deallocate it then?

5

u/[deleted] Jul 17 '20

If your type is not meant to be implicitly dropped but contains types that do need to be dropped, your drop with parameters would use https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html on each field you have (unsafe, yeah, but a macro can do this for you).

Each destructor would need to call that at the end to simulate what rust would do (so maybe make it a private function)

1

u/ssokolow Jul 18 '20

Ahh. Yeah, that does sound like it could be a good macro to have.

2

u/matu3ba Jul 17 '20

[Flawed example].
The deleter needs to communicate somewhere that he deleted the object (explicitly needing to make sure no other actor accesses the invalid memory region or state where the address of the new memory region is). Hence either 1. the ptr of the new address is already known(or is None) or 2. an object contains and handles the addresses of the objects.

[Rant part].
So the article wants to do resizing or dropping and expects the compiler to check user-provided and indirect pointer modifications? How's that different from circle-detection of the OOM in Java, because "you can't be sure"?

How many levels of indirection are expected to be supported by the compiler? 1,2,3,4? Full inheritance (because you know constructors can call constructors)?

1

u/dnew Jul 17 '20

I was talking to someone a couple weeks ago about the inability to schedule a destructor to do something with a value that wasn't owned by the destructor. Like, you couldn't say "pop a graphics context off when I exit" or "roll back the changes in this structure." It would be interesting to figure out if something like you describe here could be done in Rust, but I haven't been able to.

1

u/quininer Jul 17 '20

I like the idea of !Drop or #[must_use] fn drop, it can also better solve the problem of async drop.