DerefMove without move references aka `Box` magic for user types

Proposed api:

//(names to be changed)

trait DerefPlace{
    /// Target to deref to.
    type Target:?Sized;
    /// `Projection generated by `make_projection!()`
    const projection:Projection;
}

///creates a `Projection` for use with DerefPlace. Syntax described below.
macro_rules! make_projection{...}

struct Unique<'a,T>{...}// T is *not* required to live as long as 'a.
impl<T:?Sized DerefPlace for Unique{
    Target = T;
    const projection = None;//compiler magic
}

A make_projection invocation must start with self and is followed by at least 2 segments, which might consist of :

  • A field name, which the compiler will project into.
  • A star("*") which will cause the compiler to deref the current item. A type can only be dereferenced in a make_projection invocation implements DerefPlace (eg Box,ManuallyDrop,Unique,...).
  • A cast of the form <as $type>. This is equivalent to a pointer cast.

The last part of a make_projection invocation must be a deref(to prevent drop confusion).

For example, the implementation for Box would look like(ignoring custom allocaters):

struct Box<T:?Sized>{
    ptr:Unique<'static,T>,
}
impl<T:?Sized> DerefPlace{
    type Target = T;
    const projection = make_projection!(self.ptr.*);
}

impl<T:?Sized> Drop for Box<T>{
    fn drop(&mut self){
        //The caller is responsable for droping the contained object.
        //So we only need to free the allocated memory
        std::alloc::dealloc(
            Unique::get_ptr(&self.ptr)as *mut u8,
            std::std::alloc::Layout::for_value(&*self.ptr)
        );
    }

This would preserve all existing behaviors of Box, including being able to partially move out and being able to move back in. I can work on an implementation.

I recommend you format your code when making proposals, even if they're informal.

A macro must expand into some valid Rust syntax, and there is no current syntax or first-class concept of places in the language. Thus, the proposal is really about adding first-class places, and that's a whole separate can of worms, which requires proper design and analysis of consequences. As stated, the current proposal is basically "compiler, do magic", without any specifics on the magic required.

I'll also note that DerefMove on its own is far less desirable than &move. The latter has multiple immediate benefits (trait object safety, deprecation of unsized function parameters, ABI benefits, by-value access to allocations), while the former is a relatively niche feature which is rarely desired outside of "custom Box" support.

3 Likes

(Caveats may apply. Do not look behind the curtain. The macro-like pseudo syntax asm! will not hurt you if you don't startle it. addr_of!'s potentially forever unstable expansion doesn't impact your usage. #[rustc_box] is, morally speaking, just an optimization anyway. And so on. Exceptions make the rule, as they say.)

5 Likes

Right. But in all of those cases macros are just used as a simple way to extend the syntax of the language, instead of modifying the parser. We could introduce an asm {} block with custom syntax, and nothing would be gained over the macro solution, but we'd get nasty parser issues (new keywords, a syntactic structure which isn't used anywhere else, etc). Similarly, the difference between addr_of! and &raw is basically "do we really want a new keyword, potentially confusing sigils, and confusion between safe and raw references".

Point being, "add a macro" on its own is a non-answer for design questions. The real issue is semantics and implementation, and a macro only helps if we can already implement it in the existing language. But with first-class places, we can't, and there are more questions than answers in their design.

4 Likes