Thoughts about additional built-in pointer types

There’ve been a few extra built-in pointer types proposed, but I’ve not yet seen a single place for discussion about them. In particular, I’m thinking about the &move and &uninit (or &out) hypothetical pointer types. I would recommend reading this RFC on uninitialised pointers for information about &uninit if you haven’t already read it, and this GitHub comment thread discusses owning and uninitialised pointers (and is the earliest reference to them I can find).

One can classify these hypothetical types and the existing pointer types into a system that gives every pointer a set of properties which represent what one can do with the pointer, and what the restrictions on creating it are: uniqueness (which gives mutability), uninitialisedness (which requires moving in, and implies uniqueness), and ownership (which allows moving out, and also implies uniqueness). (I’ve considered making mutability separate from uniqueness, but it causes too many problems to be worth bothering with.) With this in mind, we have five pointer types: &, an immutable, shared pointer; &mut, a mutable, unique pointer; &move, a mutable, unique, owning pointer; and uninitialised variants of the latter two pointer types (called &uninit and &uninit move), which would be created by moving out of the corresponding initialised versions.

A key thing to realise here is that under this scheme, &move and &mut actually allow the same operations (mutating, moving out). The only difference is the type created when they are moved out of. &mut can still be moved out of, creating a &uninit, which has to be filled in again before the end of the scope. &move can be moved out of, but it creates an &uninit move, which does not have to be filled in before it dies. This (&uninit move) is where &move gets its real advantage from. (I still have no idea what &uninit move would actually be used for, though—maybe the original system with four types is easier.)


I’ve rambled enough; time to get down to the real reason why I created this issue: as a place to put the discussion about &move and &uninit (and maybe any other reference types). What are some good uses of these types? I can think of a few: implementing DerefMove<[T]> on Vec (where DerefMove provides &move equivalents of Deref), and changing FnOnce to use &move self to make trait objects make more sense. &uninit is potentially useful for a safe ‘placement box’ API. But does anyone else have any more motivation for these types, perhaps even enough for a concrete proposal some time after 1.0 to make sense?

I’ve also seen &unsafe proposed as a way to express "*mut that can’t be null". Possibly just on IRC. This would be nice to have for some unsafe APIs. In particular into_ptr and a handles-oom-for-you allocator API. Also a way for APIs to express they accept a *mut but it can’t be null (ptr::read).

&uninit is slightly problematic because of unwinding (which leaves it uninitialized).

It’s possible I misunderstood some of the discussion, but isn’t &move intended to be used such that the pointed-to object must be moved out of the &move pointer? That is, for code like this:

fn do_something(foo: &move Foo) -> () {
    //fails to compile. uncommenting the following line allows compilation to succeed:
    //drop(*foo);
}

to fail to compile unless the drop call is uncommented (which moves the value out of the pointer)? If so, that’s not obvious to me in your summary above…

My thinking was that you may move out of the &move, and don’t need to move back - unlike &mut. If you don’t move out, then the &move destructor drops the referent.

Is there interest from others here in supporting the semantics I described? I’m currently of the opinion that scope-based drop is only usually (that is, it is not always) the correct mechanism to use for safe resource clean-up… And when scope-based drop is not the right answer (as when library authors want to enforce users to make clean-up explicit - as I think should be done for MutexGuard, or other places where drop has important semantics that can affect program correctness), then the right answer (IMO) involves generating an error if the user doesn’t do something explicit to cause the variable to be dropped.

(Think this conversation belongs here, b/c pointer-types as being discussed in this thread may be a good way to enforce such an interface requirement at the API level.)

What is the advantage of &move over just passing by value? (Other than performance - but the compiler ought to be able to pass by pointer if it would be more efficient, even if it does not do so today.)

Probably relevant to my previous post here: http://discuss.rust-lang.org/t/pre-rfc-linear-type-modifier/1225

One big advantage of &move is that you can have &move T where T: ?Sized. That means that if we had &move, we could change FnOnce to take &move self, making it object-safe, allowing us to remove Thunk in favour of Box<FnOnce>. Also, if we had a trait DerefMove: Deref { fn deref_move(&move self) -> &move <Self as Deref>::Target; }, Vec-like collections could implement DerefMove<[T]>, and then all of the moving methods like into_iter could be implemented on &move [T], and built-ins like Box<[T]> would also gain those methods (effectively).

I don’t think that formulation of DerefMove works – self does not escape the deref_move call and so is dropped when it returns (at the latest), causing the Box/Vec to be deallocated, and leaving the returned &move nothing to refer to.

(The right way to formulate a DerefMove trait has been a long-time puzzler for me.)

For reference in this discussion, I proposed another pointer type (tentatively called DropPtr) to support a linear types proposal, which I haven’t seen discussed so far. DropPtr is, in my opinion, what the argument to drop wants to be:

  • Allows partial moves out (which is what I needed for my proposal, but which I think is broadly useful).
  • Ensures that drop will be invoked on all non-moved fields.
  • Referent’s memory will be cleaned up after the DropPtr goes out of scope.

At the time of you writing this, I had already solved the problem, though I’ve come to prefer different terminology (Slot would be &out/&uninit). I’m sorry I haven’t made it public yet, may have prevented some roundabout discussion.

I agree that such a type is needed, but the name is not specific enough - even something like DropContents or DropInterior would help.

I'm personally OK with those names, though I'm worried that the self: DropPtr<Self> syntax is already more verbose than &mut self for the argument to drop... I'm happy to defer to the aesthetic ideas of the community for what the right name is for the type. I'd like the name to represent that:

  • it's a pointer,
  • that ensures that its referent's unmoved fields can be cleaned up when it goes out of scope.

For what it's worth, there may be a really nice use-case for this type of pointer with a pool-allocator (managing a pool of variables of the same size and type, useful for avoiding heap fragmentation):

fn drop(self: DropPtr<_>) {
  // fully moves self.
  self.pool.free(self);
}

So that the referent would be returned to its specific allocation pool when it goes out of scope. There's probably a good reason this won't work, so far I'm just thinking out loud...

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.