Live data from Hacker News

Rust project goals: Immobile types and guaranteed destructors

github.com

31–40 of 112 posts

Re: Rust project goals: Immobile types and guaranteed destructors

#31

Earlier quoted context omitted.

Linear types requires significant work to incorporate into the core built-in collections and types. I've been following the work on Mojo to enable Linear type support for built-in types and collections, I don't think Rust's language semantics will allow for the same level of integration (Rust is already stable).

Which stdlib collections and types should become `!Move`?

None. The question is more what happens when you put a `!Move` type into, say, `Vec`, because that's a collection type that regularly moves its elements to a new allocation when it grows or shrinks.

Should it be possible to construct a `Vec` whose size can never change? Is there a subset of Vec's API that can be annotated with `where T: ?Move`? These are all important design questions, with the potential to break 99% of existing Rust code.

Re: Rust project goals: Immobile types and guaranteed destructors

#32
post #25

Earlier quoted context omitted.

But Rust has editions. That is a big lever language designers can use if they painted themselves into a corner.

Yes, editions are a great mechanism. It still has its limits, especially if you want easy edition migrations. All existing Rust code assumes it can drop any type whenever it wants, and that is not something you can just change across editions. You have to be very careful with defaults if you don't want conflicts when crossing edition boundaries.

The naïve idea would be to just say that all generic parameters have an implicit `where T: Move` bound, and you have to explicitly opt out of it with `where T: ?Move`, just like with `?Sized`.

In fact, that's exactly how I would expect it to work, but there may be non-obvious drawbacks.

Re: Rust project goals: Immobile types and guaranteed destructors

#33
Ok, I guess somebody has to provide the youngsters/uninitiated with some context. What is going on here can be viewed as part of a process of Rust (potentially) incrementally adopting the C++ model, because the Rust model is limited in important ways.

Specifically, Rust's "necessarily-trivial-destructive" moves make it possible for a memory location previously holding a valid object to become invalid without a destructor (or any other handler) being called. Accommodating this possibility resulted in unforeseen (by many) limitations, particularly in the safe subset. (See "the leakpocalypse".) This was partially addressed by the introduction of "pinning" into the Rust language. The posted github page suggests that this sort of pinning is not the ideal approach, and that it is more effective to make the "unmovability" of an object a property of the object's type, rather than a property of the reference to the object, as is the case with the pinning approach.

To be clear, we're talking about Rust-style "necessarily-trivial-destructive" movability here. Traditionally, C++ doesn't really support this sort of movability. That is, even if an object's contents are ("conceptually") moved to a different location, the original source object remains (at its original location) until it is otherwise destroyed (and its destructor called). So in C++, all types are "immovable" in the sense of the posted github page.

The github page notes how these "immovable" types can support self-references completely in the safe subset in a way that pinning can't.

> This unblocks patterns that are currently impossible in safe Rust.

For an idea of some other unblocked patterns, you can consider so-called "norad" pointers [1] (and proxy pointers [2]) in the SaferCPlusPlus library. Analogous to how `RefCell` references can be used to express references that cannot be statically verified to conform to Rust's "aliasing-xor-mutability" restrictions, "norad" pointers can be used to express references that cannot be statically verified to be lifetime safe. This would include, for example, all manner of cyclic references beyond just "self-references".

I think you could implement a version of these norad pointers in Rust that can safely target these immovable types (whose destructor is guaranteed to be called while the object is still in its original location). But note that the C++ implementation uses static inheritance (which Rust does not support) to avoid the noise having to access the target object as "interior" content (like with `RefCell`s).

With the availability of these flexible references, one could imagine immovable types becoming popular in things like games / entity component systems, GUI frameworks, browser engines, and any place where "back pointers" would be convenient. One might even imagine that at some point, types being "immovable" could become the popular default for object types in Rust (among biological and/or non-biological Rust programmers). At which point, people may decide that actually they do want (the contents of) some of their immovable types to be "movable", but they don't necessarily need the object to be destructively movable. So you could imagine the introduction of standard `nondestructive_move()` (and `nondestructive_move_from()`) methods that would be companions of the existing `clone()` (and `clone_from()`) methods. At which point Rust would have counterparts for C++ copy and move constructors (and assignment operators).

In my view, this adoption of the C++ model (potentially) addresses Rust's main limitation. With one consequence being to potentially make automated translation of C and C++ code to (reasonable code in) the safe subset of Rust much more feasible than seems to be currently.

[1] https://github.com/duneroadrunner/SaferCPlusPlus/blob/master...

[2] https://github.com/duneroadrunner/SaferCPlusPlus/blob/master...

Re: Rust project goals: Immobile types and guaranteed destructors

#34
post #30

Earlier quoted context omitted.

Pin applies to the point er , !Move applies to the point ee .

So... Pin should be defined as Pin ?

No, the point of Pin is to wrap types that CAN move. If the type were !Move then Pin wouldn't be needed.

Re: Rust project goals: Immobile types and guaranteed destructors

#35
post #22

Could someone explain this to me as someone who's never touched async Rust? What kind of useful patterns would this allow for?

Things you'd expect to work already. It doesn't really add anything new and flashy, but removes some annoying warts. Sync code has scoped threads that enable multi-threaded execution within a function, without having to ensure the data outlives the function call. Async can't do that while guaranteeing safety. This makes tokio::spawn awkward and annoying, and is a major source why people dislike Rust's async. Low-leve…

> and is a major source why people dislike Rust's async

It's worth mentioning that there is, in fact, no language out there other than Rust that can even do this in the first place.

Some languages give the illusion that they support it by boxing the stack frame of async functions and letting a garbage collector deal with the consequences, but that comes with significant drawbacks too (additional GC pressure, heap allocation overhead, requiring a GC in the first place).

You can do it with C++ coroutines, but it's much harder to do correctly than in Rust if you want to maintain any sense of conviction that the system is correct.

The main reason that structured async concurrency would be so awesome to have is that it feels like Rust has the right set of features that could enable it with a set of constraints that are so much more attractive than any other language out there can provide - no overhead, "just works" with no drawbacks.

(For the record, you can actually get pretty far today using primitives like `FuturesUnordered` instead of `tokio::spawn` and similar, but this sidesteps the runtime's scheduler, so YMMV. This basically creates a task-local mini-scheduler for your futures, which may or may not be sufficient.)

Re: Rust project goals: Immobile types and guaranteed destructors

#36
post #4

Could someone explain this to me as someone who's never touched async Rust? What kind of useful patterns would this allow for?

It makes it easier to write recursive async functions. It makes it easier for async functions to borrow rather than clone from their outer scope. All really awesome, non controversial and ergonomic things.

Wait, how does it actually change the recursive async story?

The problem today is that the compiler-synthesized struct implementing `Future` for each async function cannot contain an instance of itself without boxing, because it would create a type of infinite size. That's a separate problem that's also hard to solve nicely, because the call tree might be deep, and deciding where to cut (using Box::pin) is non-trivial.

Re: Rust project goals: Immobile types and guaranteed destructors

#37
post #34
post #30

Earlier quoted context omitted.

So... Pin should be defined as Pin ?

No, the point of Pin is to wrap types that CAN move. If the type were !Move then Pin wouldn't be needed.

I guess `!Move` is largely equivalent to `Unpin` for the purposes of `Pin`, so for example Pin's safe constructor `Pin::new()` can be re-expressed in terms of `!Move` instead of `Unpin`. Today you need unsafe code to pin a `!Unpin` (i.e. "movable") type.

But I also suspect there are important differences between `!Move` and `Unpin` that I'm not sure about.

Re: Rust project goals: Immobile types and guaranteed destructors

#38
Little by little, Rust, like D, acknowledges that C++ flexibility regarding object construction, copy and move, even if too much as a default, is sometimes needed :)

I saw in D years ago how they also checked into this flexibility after getting some use cases for it (in this case, copying): https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1...

Re: Rust project goals: Immobile types and guaranteed destructors

#39

Ok, I guess somebody has to provide the youngsters/uninitiated with some context. What is going on here can be viewed as part of a process of Rust (potentially) incrementally adopting the C++ model, because the Rust model is limited in important ways. Specifically, Rust's "necessarily-trivial-destructive" moves make it possible for a memory location previously holding a valid object to become invalid without a destru…

I agree with most of that, but there's one important detail (that I'm sure the designers of this proposal are thinking about): The current contract of Pin in Rust (the only standard support for expressing immobility) doesn't just cover the object's own location, but also any references derived from the object, commonly called "pin projection".

For example, if you have a `Pin>>`, it's safe to turn that into a `Pin`.

Any system that replaces `Pin` will probably have to maintain that same property, which wouldn't naïvely happen using C++-like move semantics, right? Or maybe I'm overassuming?

Re: Rust project goals: Immobile types and guaranteed destructors

#40

More algebraic effects being retrofitted onto Rust.

How so? This feel distinct from the "algebraic effects"-like features like constness, async, can-panic, can-unwind, etc., since this is a property of the types themselves rather than of functions.

The traits are essentially effect handlers for effects like `drop`, `move`, `forget` which are implicitly charged to the a function which owns a `T` and does drops, moves, or forgets it.

Inferring the capabilities of the function from the traits of the types of the arguments is similar to tracking effects. The function charges `drop` when `x: T` goes out of scope, which is handled by the trait implementation. If Rust had a proper algebraic effects type system, you would be able to see this directly in the signature of the function (and even more, if the trait impls themselves had their effects tracked, you'd be able to see from the signature of the function the side effects of deallocation of its owned variables, like if `drop` performs `io`).

Post reply on HN