Feature: Allow pattern-matching of a pointer

Currently there is no easy and safe way to get a pointer to a field from a pointer to an enum or struct. There has been a lot of talk adding C like arrow operator ->

which solves the problem:

struct Baz {
    a: u32,
    b: f32,
}

let baz: *const Baz = /* .. */; // not-a-valid-Baz

// let baz_a: *const u32 = &baz.a as *const u32; // danger! UB

let baz_a: *const u32 = baz->a;

However similar would not work for enums (probably, at least I have not herd of such proposals)

let foo: *const Option<u32> = /* .. */;

let foo_: *const u32 = foo.Some->0; // best I can think of

Feature: Allow pattern-matching of a pointer

This is similar to how &raw [const | mut] allows taking pointers to fields without going trough an intermediate references.

Dereferencing a ptr to an enum

let foo: *const Option<u32> = /* .. */;

// the deref of `foo` is unsafe since the tag of the enum must be valid
unsafe {
    match *foo {
        // new syntax, similar to `&raw [const | mut]` and does not go trough reference
        Some(raw ref ptr) => {
    
            let val: &u32 = &*ptr,
        
        },
        None => {},
    }
}

Dereferencing a ptr to a struct

let baz: *const Baz = /* .. */; // totally-not-safe-Baz

// the deref of `baz` is safe since we only bind to pointers
match *baz { 
    Baz { 
        raw ref a,
        raw ref b,
    } => {
        let a: &u32 = unsafe { *a };
        let b: &f32 = unsafe { *b };    
    }
}

// allow mismatching references and pointers? 
// SAFETY:
// foo.b must be valid
unsafe {
    match *baz {
        Baz { 
            raw ref a,
            ref b,
        } => {
            let a: &u32 = *a;
            let b: &f32 = b;    
        }
    }
}

One of the benefits of this feature is to allow building large structs piece by piece onto the heap

let mut uninit: Box<MaybeUninit<Baz>> = Box::new_uninit();

let ptr: *mut Baz = (&mut *uninit as *mut MaybeUninit<Baz> as *mut Baz);

let Baz { raw ref a, raw ref b } = *ptr;

unsafe {
    core::ptr::write(a, 0);
    core::ptr::write(b, 0.0);
}

let mut baz = unsafe { baz.assume_init() };

EDIT: Fixed unsafe blocks to make more sense

1 Like

The scope of the unsafe blocks in your examples wouldn’t work. E.g. look at this example of how match works with references

fn main() {
    let x = Some("hi".to_string());
    let y = &x;
    match *y {
        Some(_) => {}
        _ => {}
    }
    // doesn’t work, since using a block means that `*y` would be moved
    match {*y} {
        Some(_) => {}
        _ => {}
    }
}

(playground)

Since an unsafe {} block is still a block, something like

doesn’t make too much sense. Furthermore the fact that the pattern outside of the unsafe block determine whether unsafe is needed for the dereference operation in the first place seems weird anyways.

So it would instead need to be

let foo: *const Option<u32> = /* .. */;

// the deref of `foo` is unsafe since the tag of the enum must be valid
unsafe {
    match *foo {
        // new syntax, similar to `&raw [const | mut]` and does not go trough reference
        Some(raw ref ptr) => {

            let val: &u32 = unsafe { &*ptr },

        },
        None => {},
    }
}

A bit unfortunate since now the whole right-hand-side of every match arm is inside of the unsafe block, too, and you can’t do much to make the use of unsafe more fine-grained.

2 Likes

Ah I see, such a shame

Previously: RFC for a match based surface syntax to get pointer-to-field by HeroicKatora · Pull Request #2666 · rust-lang/rfcs · GitHub

If this is to be specified, you'll need to specify how it interacts with default binding modes (as this is basically a new raw ref binding mode). Seeing as it's safe, can you match on a pointer directly and bind pointers to its fields without & and raw ref? Does & in the pattern deref the raw pointer?

That, as this is a feature for unsafe code, it doesn't have a default binding mode would be an arguable position, but there needs to be a position explicitly taken and supported.

I now think you should only be allowed to go from pointer to pointer, so allowing default binding modes should not cause any sneaky unsoundess. (as far as I can come up with)

Maybe you should be able to do unsafe match to do a pointer match without making the branches automatically be allowed to do unsafe operations?