Skip to content

Validity of Non-matched Patterns #587

@chorman0773

Description

@chorman0773

I was wondering what the rules are on binding pattern validity, namely when a pattern does not succesfully match.

The main example I can think of are custom ADTs (ADTs using union-of-struct definitions, rather than enum)

Using that example, the specific question I want to ask whether the following code (with bar() as the entry point) is guaranteed to be well-defined (or if it is UB, or might be UB depending on match lowering):

#[repr(C)]
pub union FakeEnum {
     pub fake_variant: FakeVariant,
     pub other_variant: OtherVariant,
     // other fields
}

pub struct OtherVariant {
    pub discriminant: u32,
    pub blank: u32,
}

pub struct FakeVariant {
     pub discriminant: u32,
     pub value: NonZeroU32,
}


pub unsafe fn foo(x: FakeEnum) {
     unsafe {
           match x {
               FakeEnum{fake_variant: val @ FakeVariant{discriminant: 0, ..}} => println!("{}", val.value),
               _ => {}
     }
}

pub fn bar() {
     foo(FakeEnum{other_variant: OtherVariant{discriminant: 1, blank: 0})
}

Because FakeVariant is matched by binding, one might expect that it eagerly requires validity (which causes UB on reading 0 bytes for value). However, since when discriminant is checked, the pattern match fails, possibly preventing value from being read at all.

For context of this, in the OS I'm working on, there is a concept of an "Option" (not in the optional sense like std::option::Option, but in the sense of "A list of options"), which roughly follows the union-of-struct pattern for two main reasons:

  • The "discriminant" of the option is actually a UUID (#[repr(Uuid)] does not exist in Rust), and
  • There is no fixed list of the set of options that may be passed to the kernel (explicit functionality exists for "If you don't know what this option is, ignore it").

(There is also a header of common fields that carries more data than just the discriminant, notably a flags field).

It would be nice, for ergonomics reasons (and to make correctness easier) if I could write matches that check if the type matches an expected value, and if so, bind the whole union field, which would use a form similar to the above. However, I worry about validity, considering that while all option types of a given group have a fixed upper size (32 or 64 bytes for the data after the header is common), there is no guarantee that any given option type uses the whole payload (and thus might have tail padding, or interior padding after the header).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions